quinta-feira, 28 de dezembro de 2017

quinta-feira, 21 de dezembro de 2017

[CKEditor] ModuleNotFoundError: No module named 'django.core.urlresolvers'

No django 2.0, o reverse mudou de pacote.

Altere no arquivo [SEU_ENV]/lib/python3.6/site-packages/ckeditor_uploader/widgets.py a linha:


#from django.core.urlresolvers import reverse

Para:

from django.urls import reverse

hasta!

quarta-feira, 1 de novembro de 2017

Limpar a lista de conexões recentes do SSH no New Remote Connection

Em uma janela no terminal, digite:

defaults write "com.apple.Terminal" PreviousCommands '()'

Hasta!

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();
            }

        });
    }
});

sexta-feira, 9 de junho de 2017

Como pegar o ID do último commit no GIT para forçar atualização de arquivos

Normalmente os navegadores modernos fazem cache dos arquivos para diminuir banda utilizada e aumentar o desempenho.

Isso é sempre uma boa, a não ser quando vc precisa forçá-lo a atualizar, principalmente quando alguma mudança é feita em CSS e JS.

No mobile ainda é mais custoso, haja visto que muitos usuários não sabem como limpar o cache e forçar o recarregamento.

Para isso, podemos "mudar" a url do arquivo com cache, para forçá-lo a atualizar e com isso obtermos o resultado desejado.

No seu arquivo de tags, inclua a simple_tag abaixo:

@register.simple_tag
def git_ver():
    '''
    Retrieve and return the latest git commit hash ID and tag as a dict.
    '''
    import os, subprocess
    from django.conf import settings

    git_dir = os.path.dirname(settings.PROJECT_PATH)

    try:
        # Date and hash ID
        head = subprocess.Popen(
            "git --git-dir={dir}/.git rev-parse HEAD".format(dir=git_dir),
            shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        version = head.stdout.readline().strip().decode('utf-8')
        git_string = "{v}".format(v=version)
    except:
        git_string = u'unknown'

    return git_string


E no seu template:

{% load site_tags %}
<link href="styles.css?v={% git_ver %}" rel="stylesheet">

hasta!

sexta-feira, 5 de maio de 2017

'Settings' object has no attribute 'TEMPLATE_DEBUG'

Para quem atualizou a versão do Django para 1.11 e utiliza a sorl-thumbnail, a versão disponível desse plugin no PyPI ainda não contempla a setting TEMPLATE como um dicionário, ocasionando o erro do título deste post pela falta da setting TEMPLATE_DEBUG.

Caso opte por colocar no arquivo settings.py a setting obsoleta, será gerado o warning abaixo:


?: (1_8.W001) The standalone TEMPLATE_* settings were deprecated in Django 1.8 and the TEMPLATES dictionary takes precedence. You must put the values of the following settings into your default TEMPLATES dict: TEMPLATE_DEBUG.

A solução (provisória) é instalar a Sorl direto do github onde este problema foi resolvido até que ela seja atualizada no PyPI.


Execute comando abaixo para a instalação:

pip install -e git+https://github.com/mariocesar/sorl-thumbnail.git#egg=sorl-thumbnail

O comando acima irá desinstalar a versão que ainda apresenta este problema e baixará a versão presente no git.

Atualize também seu arquivo requirements.txt com o comando acima (sem o pip install) para usar em produção.

hasta!

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!


terça-feira, 6 de setembro de 2016

Validação customizada usando o ModelForm

Sempre existe alguma regra de validação particular de algum form. Seja um campo obrigatório caso outro seja preenchido, etc.

Vejamos como fazer isso de um jeito bem fácil:


class SeuModelForm(forms.ModelForm):
 campo1 = forms.CharField(required=False, widget=forms.TextInput(),)
 campo1a = forms.BooleanField(required=False, widget=forms.CheckboxInput(),)
 campo2 = forms.CharField(required=False, widget=forms.TextInput(),)
 campo2a = forms.BooleanField(required=False, widget=forms.CheckboxInput(),)
 campo3 = forms.BooleanField(required=False, widget=forms.CheckboxInput(),)

 class Meta:
  model = SeuModel
  fields = '__all__'



 #def clean_aceito(self):
 # campo3 = self.cleaned_data['campo3']
 #  if not campo3:
 #   raise forms.ValidationError("Mensagem de erro do campo3")
 #  return campo3




 def clean(self):
  cleaned_data = super(SeuModelForm, self).clean()

  campo1 = cleaned_data.get("campo1")
  campo1a = cleaned_data.get("campo1a")

  campo2 = cleaned_data.get("campo2")
  campo2a = cleaned_data.get("campo2a")

  campo3 = cleaned_data['campo3']

  if not campo1 and not campo1a:
   self.add_error('campo1', u'Mensagem de erro do campo1')


  if not campo2 and not campo2a:
   self.add_error('campo2', u'Mensagem de erro do campo2')

  if not campo3:
   self.add_error('campo3', u'Mensagem de erro do campo3')

Acima vemos duas possibilidades:

  • Validar um campo específico, através do método clean_<campo>
  • Validar o form todo, testando todos campos juntos com o método clean

hasta!

sexta-feira, 19 de agosto de 2016

Erros Frequentes: Django==1.9.8 + django-ckeditor-updated==4.4.4 No module named util

Peguei o seguinte erro:


Traceback (most recent call last):
  File " in="" line="" manage.py="" module="">    execute_from_command_line(sys.argv)
  File "/var/www/SEU_SITE/ENV/local/lib/python2.7/site-packages/django/core/management/__init__.py", 
line 353, in execute_from_command_line
    utility.execute()
  File "/var/www/SEU_SITE/ENV/local/lib/python2.7/site-packages/django/core/management/__init__.py", 
line 327, in execute
    django.setup()
  File "/var/www/SEU_SITE/ENV/local/lib/python2.7/site-packages/django/__init__.py", line 18, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/var/www/SEU_SITE/ENV/local/lib/python2.7/site-packages/django/apps/registry.py", line 108, in populate
    app_config.import_models(all_models)
  File "/var/www/SEU_SITE/ENV/local/lib/python2.7/site-packages/django/apps/config.py", 
line 202, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/var/www/SEU_SITE/app/blog/models.py", line 11, in 
    from ckeditor.fields import RichTextField
  File "/var/www/SEU_SITE/ENV/local/lib/python2.7/site-packages/ckeditor/fields.py", line 4, in 
    from ckeditor.widgets import CKEditorWidget
  File "/var/www/SEU_SITE/ENV/local/lib/python2.7/site-packages/ckeditor/widgets.py", line 10, in 
    from django.forms.util import flatatt
ImportError: No module named util

Para resolver, basta editar o arquivo /var/www/SEU_SITE/ENV/local/lib/python2.7/site-packages/ckeditor/widgets.py como abaixo:



Troque a linha:
django.forms.util import flatatt

Por:
django.forms.utils import flatatt

sexta-feira, 12 de agosto de 2016

Plugins Úteis: django-image-cropping - Como incoporar uma ferramenta de recorte para fotos no admin


Imagine que temos um projeto onde o usuário, responsável por alimentar os conteúdos, não tem a mínima noção do tamanho da imagem necessária para não destruir o layout, e nenhum pouco de boa vontade para ler a especificação do tamanho no help_text?

Claro que sempre dá pra fazer o recorte automático com o sorl-thumbnail, mas e se o usuário quiser escolher como recortar a foto?

Bom, pra isso, apresento-lhes o impressionante : django-image-cropping.


Vejamos como utilizar:

1. Primeiramente, instale ele no seu virtualenv:

pip install django-image-cropping

2. Se não tiver o easy_thumbnails instalado, instale:

pip install easy_thumbnails 

3. Adicione os dois no INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    'easy_thumbnails',
    'image_cropping',
    ...
]

4. Adicone também no settings.py:

from easy_thumbnails.conf import Settings as thumbnail_settings
THUMBNAIL_PROCESSORS = (
    'image_cropping.thumbnail_processors.crop_corners',
) + thumbnail_settings.THUMBNAIL_PROCESSORS

5. No seu model que quiser a função de recorte no admin deixe como a seguir:

from image_cropping import ImageRatioField

class SeuModel(models.Model):
    ...
    imagem = models.ImageField(upload_to="/caminho/para/upload/",)
    cropping = ImageRatioField('imagem', '900x900')
    ...


Mude a proporção do atributo cropping de acordo com sua necessidade.

6. No admin.py:

from django.contrib import admin
from image_cropping import ImageCroppingMixin

class SeuModelAdmin(ImageCroppingMixin, admin.ModelAdmin):
    pass

admin.site.register(SeuModel, SeuModelAdmin)

7. No template que for exibir a foto recortada utilize a tag provida pelo plugin:

{% cropped_thumbnail instancia_do_seu_model "cropping" [scale=INT|width=INT|height=INT|max_size="INTxINT"] %}

EX:
<img cropping="" scale="0.5" src="{% cropped_thumbnail instancia_do_seu_model " />


8. Dá inclusive para utilizar esse plugins para Inlines:


from image_cropping import ImageCroppingMixin
class ImagemInline(ImageCroppingMixin, admin.TabularInline):
 model = Imagem
 extra = 0



Desta maneira, ao fazer o upload, o usuário poderá selecionar a área de recorte diretamente no admin, respeitando a proporção imposta no models.py e sem destruir o layout.


Fonte e mais informações: https://pypi.python.org/pypi/django-image-cropping


hasta!

quarta-feira, 20 de abril de 2016

Como esconder alguns registros do changelist do admin do Django



A pergunta é simples: Pra que eu um dia iria precisar esconder alguns registros do change list, se já estou no admin como superuser?

Bom, imagine que seu cliente quer o acesso ao admin para gerenciar os conteúdos e também acesso a alguma seção do projeto que o login é necessário.

Quão surpreso você ficaria se descobrisse, que aquele seu cadastro de teste onde vc conseguia entrar para ajudar, testar e acompanhar o andamento do projeto fosse removido?

Isso, apesar de ridículo, não é de todo o impossível, e foi por passar por algo semelhante que escrevo este post.


Para ocultar os registros de uma determinada classe, basta  sobrescrever o método get_queryset do admin daquela classe no admin.py:

class CadastroAdmin(admin.ModelAdmin):
 form = CadastroForm
 search_fields = ('nome',)
 list_display = ('nome', 'email', 'unidade','ativo',)
 list_filter = ['ativo','unidade']
 list_editable = ['ativo',]
 exclude = ('user',)
 save_on_top = True


 def get_queryset(self, request):
  qs = super(CadastroAdmin, self).get_queryset(request)
  if request.user.is_superuser:
   return qs
  return qs.exclude(id__in=[13,14,15])

admin.site.register(Cadastro, CadastroAdmin)

No exemplo acima, quando o usuário logado for superuser do admin, este verá todos os registros, do contrário, a queryset excluirá dos resultados os cadastros com ids 13, 14 e 15.

Com pequenos ajustes, este método tornar bastante útil para evitar dedos inquietos clicando onde não devem.


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>

segunda-feira, 1 de fevereiro de 2016

Como utilizar o "Salvar e adicionar outro" do Admin no Front com Class-Based Views

O botão de Salvar e Adicionar outro é muito útil no administrativo quando se faz necessário inserir uma lista de dados em algum modelo.

No front, quando o usuário tem permissão de inserir algo para persistir o banco, também é possível utilizar este recurso. Vejamos como:

views.py

class SuaClasseCreateView(CreateView):
	form_class = SuaClasseForm
	success_url = '/url-de-sucesso/'
	template_name = 'form.html'
	model = SuaClasse

	def post(self, request, *args, **kwargs):

		form_class = self.get_form_class()
		form = self.get_form(form_class)

		if form.is_valid():

			if request.POST.get('_addanother'):
				self.success_url = '/url-de-nova-insercao/'

			return self.form_valid(form)
		else:
			return self.form_invalid(form)


	def form_valid(self, form):
		form.save()
		return HttpResponseRedirect(self.success_url)

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



class SuaClasseUpdateView(UpdateView):
	form_class = SuaClasseForm
	model = SuaClasse
	success_url = '/url-de-sucesso/'
	template_name = 'form.html'


	def post(self, request, *args, **kwargs):
		self.object = self.get_object()
		form_class = self.get_form_class()
		form = self.get_form(form_class)
		form.instance = self.object

		if form.is_valid():

			if request.POST.get('_addanother'):
				self.success_url = '/url-de-nova-insercao/'

			return self.form_valid(form)
		else:
			return self.form_invalid(form)


	def form_valid(self, form):
		form.save()
		return HttpResponseRedirect(self.success_url)

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

As views acima funcionam para qualquer modelo, sem nenhuma alteração nos forms.py. No html, apenas é necessário incluir o botão de Salvar e adicionar outro como exemplo abaixo:

form.html
<input type="submit" name="_addanother" value="Salvar e adicionar outro(a)">


Quando o clique é feito neste botão, o value dele é enviado via post, e a tratativa disso está no método post da view. Quando não é clicado, o valor não é enviado, não redirecionando assim para uma nova tela com o formulário aberto.

hasta!

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!

quarta-feira, 2 de setembro de 2015

Desmistificando Forms: CreateView e UpdateView com o trabalho pesado


O básico do CreateView

Começando do básico, o exemplo abaixo já quebra um galho enorme quando de trata de formulários simples, como por exemplo o Contato de algum site.

forms.py
# coding: utf-8
from django import forms

from .models import Contato

class ContatoForm(forms.ModelForm):
 nome = forms.CharField(widget=forms.TextInput(attrs={'class' : 'required',}), label="Nome")
 email = forms.EmailField(widget=forms.TextInput(attrs={'class' : 'required',}), label="E-mail")
 texto = forms.CharField(widget=forms.Textarea(attrs={'class' : 'required',}), label="Texto")


 class Meta:
  model = Contato
  fields = '__all__'

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

Até aqui sem muitas dificuldades né? Vemos a definição da classe para gerenciamento do form, com alguns atributos. Com o fields = '__all__' não era necessário definir os atributos, mas isso é obrigatório caso queira adicionar uma classe, como por exemplo o required ou outro atributo qualquer, por exemplo um placeholder, etc.

views.py
# Create your views here.
# coding: utf-8
from django.views.generic.edit import CreateView

from .forms import ContatoForm
from .models import Contato


class ContatoView(CreateView):
 form_class = ContatoForm
 success_url = '/contato/sucesso/'
 template_name = 'contato/contato.html'
 model = Contato

 def get_context_data(self, **kwargs):
  kwargs.update({
   'menu':'contato',
   'title': 'Contato',
  })
  return kwargs

A view também é bem tranquila. Herdando as características da CreateView, exige apenas algumas informações para resolver o problema de inserção e validação dos campos de forma elegante:

  • form_class: Classe do Form definida no forms.py
  • success_url: URL para qual será redirecionado após o sucesso da inserção
  • template_name: Html do form.
  • model: Classe definida no models.py

contato.html
<form method="post" action="">
    {% csrf_token %}
    {{ form.errors}}
    {{ form.as_p }}
    <input class="btn" type="submit" value="Enviar">
</form>

Acima temos um exemplo bem sucinto de como montar um form de forma bem automágica e podemos observar algumas coisas interessantes com o exemplo acima:

  • A CreateView trabalha com o post para própria página (mesma url) por isso no action do form não tem nenhuma informação. 
  • A validação do CSRF já é nativa da CreateView, a unica coisa que precisa e incluir o token no html dentro da tag form.
  • {{ form.errors }} vai gerar uma <ul> com a tag errorlist onde cada <li> será responsável por lista os erros de um campo específico e terá uma <ul> também com a classe errorlist uma lista de <li> para todos os erros deste respectivo campo.
Outra coisa legal de fazer é definir no html os campos separamente para um tratamento de erro e layout melhor apresentados. Isso pode ser feito assim:

<p>{{ form.email }}</p>
<div class="error">{{ form.email.errors }}</div>


E o UpdatView?

Um formulário de contato não sofrerá update bem possivelmente. Mas apenas para ilustar usaremos o e mesmo exemplo.


views.py
class ContatoUpdateView(UpdateView):
 form_class = ContatoForm
 model = Contato
 success_url = '/contato/sucesso/'
 template_name =  'contato/contato.html'

 def get_context_data(self, **kwargs):
  kwargs.update({
   'menu':'contato',
   'title': 'Contato',
   'update':True,
  })
  return kwargs

Como assim só isso? Pois é. Só isso. E sim o HTML é exatemente o mesmo. Se usar algum recurso para gerar o form como o {{ form.as_p }}, {{ form.as_table }} é só isso e o mesmo html acima. Caso defina os campos individualmente, lembre-se de colocar {{ form.id }} para validar a instancia que está sendo editada.


Eu já mostrei como fazer para utilizar os inlines junto com a CreateView neste post: http://djangoweb.blogspot.com.br/2013/08/como-utilizar-os-inlines-no-front-para.html. Agora vamos a algo mais divertido. 

Que tal criarmos um exemplo mais complexo, onde não terá apenas uma, mas sim várias classes inline, com o CreateView e UpdateView?


Multiplos inlines


forms.py
# coding: utf-8
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.models import inlineformset_factory

from django import forms
from .models import *

from util import util as U

class ModeloForm(forms.ModelForm):
 
 class Meta:
  model = Modelo
  fields = '__all__'

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


class ModeloInline1Form(forms.ModelForm):

 class Meta:
  model = ModeloInline1
  fields = '__all__'

ModeloInline1FormSet = inlineformset_factory(Modelo, ModeloInline1, extra=0, min_num=1, form=ModeloInline1Form, fields='__all__')

class ModeloInline2Form(forms.ModelForm):

 class Meta:
  model = ModeloInline1
  fields = '__all__'

ModeloInline2FormSet = inlineformset_factory(Modelo, ModeloInline2, extra=0, min_num=1, form=ModeloInline1Form, fields='__all__')


views.py
class ModeloCreateView(CreateView):
 form_class = ModeloForm
 success_url = '/home/'
 template_name = 'modelo/form.html'
 model = Modelo

 def get(self, request, *args, **kwargs):
  self.object = None
  form_class = self.get_form_class()
  form = self.get_form(form_class)
  inline1_form = ModeloInline1FormSet()
  inline2_form = ModeloInline2FormSet()
  return self.render_to_response(self.get_context_data(form=form, inline1_form = inline1_form , inline2_form = inline2_form ))
 def post(self, request, *args, **kwargs):
  self.object = None
  form_class = self.get_form_class()
  form = self.get_form(form_class)
  inline1_form = ModeloInline1FormSet(self.request.POST)
  inline2_form = ModeloInline1FormSet(self.request.POST)
  if (form.is_valid() and inline1_form .is_valid() and
   inline2_form .is_valid()):
   return self.form_valid(form, inline1_form , inline2_form )
  else:
   return self.form_invalid(form, inline1_form , inline2_form )

 def form_valid(self, form, inline1_form, inline2_form ):
  self.object = form.save()
  inline1_form.instance = self.object
  inline1_form.save()
  inline2_form.instance = self.object
  inline2_form.save()
  return HttpResponseRedirect(self.get_success_url())

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


 def get_context_data(self, **kwargs):
  kwargs.update({})
  return kwargs


class ModeloUpdateView(UpdateView):
 form_class = ModeloForm
 model = Modelo
 success_url = '/home/'
 template_name =  'modelo/form.html'

 def get(self, request, *args, **kwargs):
  self.object = self.get_object()
  form_class = self.get_form_class()
  form = self.get_form(form_class)

  # Render form
  inline1_form = ModeloInline1FormSet(instance=self.object)
  inline2_form = ModeloInline2FormSet(instance=self.object)
  return self.render_to_response(self.get_context_data(form=form,inline1_form=inline1_form,inline2_form=inline2_form))

 def post(self, request, *args, **kwargs):
  self.object = self.get_object()
  form_class = self.get_form_class()
  form = self.get_form(form_class)
  inline1_form = ModeloInline1FormSet(self.request.POST, instance=self.object)
  inline2_form = ModeloInline2FormSet(self.request.POST, instance=self.object)
  if (form.is_valid() and inline1_form.is_valid() and inline2_form.is_valid()):
   return self.form_valid(form, inline1_form, inline2_form)
  else:
   return self.form_invalid(form, inline1_form, inline2_form)

 def form_valid(self, form, inline1_form , inline2_form ):
  self.object = form.save()
  inline1_form.instance = self.object
  inline1_form.save()
  inline2_form.instance = self.object
  inline2_form.save()
  return HttpResponseRedirect(self.get_success_url())

 def form_invalid(self, form, inline1_form , inline2_form ):

  return self.render_to_response(
   self.get_context_data(
    form=form,
    inline1_form=inline1_form,
    inline2_form=inline2_form)
   )

 def get_context_data(self, **kwargs):
  kwargs.update({
   'update':True,
  })
  return kwargs

Nos exemplos acima, o formulário html pode ser o mesmo como no exemplo do Contato. Podemos também utilizar o recurso do inline visto no post cima citado para inserção de quantos inlines forem necessários com o mesmo recurso do admin para sempre adicionar mais um conforme necessidade.

Um jeito mais elegante de usar isso é usar um template vinculado ao js, com o plugin underscore disponível aqui: http://underscorejs.org/

<script type="text/javascript" src="/static/site/js/plugins/underscore/underscore-min.js"></script>
<script type="text/html" id="modeloinline1-template">
  <div class="bloco-modeloinline1 clearfix">
   <div class="col-lg-12">
    <div class="form-group">
     <label for="">Exemplo de Textarea</label>
     <textarea rows="5" name="modeloinline1_set-<%= id %>-descricao" id="id_modeloinline1_set-<%= id %>-descricao" cols="40" class="form-control"></textarea>
    </div>
   </div>
  </div>
 </script>

django.jQuery('.container-modeloinline1').on('click', '.btn-adicionar-modeloinline1', function(ev) {
    ev.preventDefault();
    django.jQuery(this).parent().find('button').hide();
    var count = django.jQuery('.container-modeloinline1').children().length;
    var tmplMarkup = django.jQuery('#modeloinline1-template').html();
    var compiledTmpl = _.template(tmplMarkup, { id : count });
    django.jQuery('.modeloinline1-form"').append(compiledTmpl);
    // update form count
    django.jQuery('#id_modeloinline1_set-TOTAL_FORMS').attr('value', count+1);
   });
</script>
<form role="form" action="" method="post" class=''>

 {{ modeloinline1.management_form }}
 <div class="container-modeloinline1-form">
 {% for form in inline1_form %}
  {% if update %}
   {{ form.id }}
   {{ form.avaliacao }}
  {% endif %}
  {% if form.errors %}
       <div class="alert alert-danger">
                     Por favor corriga os erros abaixo para prosseguir:
                     {{ form.errors}}
               </div>
         {% endif %}
 </div>
</form>
<button class="btn btn-primary btn-sm btn-adicionar-modeloinline1" type="button">Adicionar outro Modeloinline1</button>

quinta-feira, 20 de agosto de 2015

Como usar o filter_horizontal/filter_vertical fora do admin do Django


Tem um ManyToManyField em sua classe e deseja usar o fantástico recurso do filter_horizontal/filter_vertical em um formulário no front? Vou lhe dizer como.

No html, deixe como a seguir:

{% load static from staticfiles %}
<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <link rel="stylesheet" href="/static/admin/css/widgets.css">
</head>
<body>
 <form action="" method="post">
  <!-- Não esqueca de colcoar o atributo multiple="multiple" -->
  <select name="seu_campo" id="id_seu_campo"  multiple="multiple">
   <option value="1">1</option>
   <option value="2">2</option>
   <option value="3">3</option>
   <option value="4">4</option>
   <option value="5">5</option>
   <option value="6">6</option>
  </select>
   
  <input type="submit">
 </form>

 <script src="{% static "site/js/jquery.js" %}"></script>
 <script type="text/javascript" src="{% static "admin/js/jquery.init.js" %}"></script>
    <script type="text/javascript" src="/admin/jsi18n/"></script>
    <script type="text/javascript" src="{% static "admin/js/core.js" %}"></script>
    <script type="text/javascript" src="{% static "admin/js/SelectBox.js" %}"></script>
    <script type="text/javascript" src="{% static "admin/js/SelectFilter2.js" %}"></script>
    <script type="text/javascript">
     // PARAMETROS DO SELECT FILTER:
     // ID do seu elemento select
     // NAME do seu elemento select
     // 0 - para filter_horizontal / 1 para filter_vertical
     // Caminho do admin para source de imagens utilizadas

        addEvent(window, "load", function(e) {SelectFilter.init("id_seu_campo", "seu_campo", 0, "/static/admin/"); });
    </script>

</body>
</html>
hasta!

sexta-feira, 14 de agosto de 2015

Plugins Úteis: django-auditlog - Ótima solução para registro de logs



Precisa de um registro de logs para todas as alterações realizadas em um sistema? Isso normalmente é necessário para sistemas que sofrerão auditoria.

O caminho das pedras é duro, alterar todas as views, fazer os registros manualmente, coonsiderar se quer apenas registrar qual atributo foi alterado ou pior, registrar qual era a informação antiga e qual é a informação nova de cada atributo. Trabalhoso? Muito.

Eis que surge o django-auditlog (https://github.com/jjkester/django-auditlog). Com ele o log fica bem completo e é muito fácil de configurar.

Você vai precisar de:
  • Python 2.7 ou 3.4 
  • Django 1.7 ou 1.8 

Vamos a um passo a passo bem simples para colocar o plugin pra funcionar:


1. Instale ele no seu virtualenv:
pip install django-auditlog

2. Adicione ele na setting INSTALLED_APPS:
...
'auditlog'
...

3. Rode o comando migrate para criar as tabelas necessárias no banco:
manage.py migrate

4. Adicione a linha abaixo na setting MIDDLEWARE_CLASSES:
...
'auditlog.middleware.AuditlogMiddleware'
...

5. Pronto, agora é só registrar os modelos que quer manter o histórico de mudanças como no examplo abaixo:
from auditlog.registry import auditlog
from django.db import models

class MyModel(models.Model):
    pass
    # Model definition goes here

auditlog.register(MyModel)


Depois de tudo configurado, terá sido criado uma tabela no seu banco de dados com o nome de auditlog_logentry,  com os seguintes campos:

  • id
  • object_pk
  • object_id
  • object_repr
  • action
  • changes
  • timestamp
  • actor_id
  • content_type_id
  • remote_addr
  • additional_data


Algumas considerações importantes:

  • object_pk e object_id armezenam a chave primária e o id do registro em questão. Caso sua classe não possua uma chave primária definida, estes dois atributos terão o mesmo valor: a chave primária ID criada automaticamente pelo Django na ausencia de uma definida.
  • object_repr será o valor definido no método __unicode__ da classe do objeto em questão.
  • action será um valor númerico (0,1 ou 2), onde o mais valor é considerado a atividade mais intrusiva no banco, logo em ordem temos: Inserção (0), Alteração (1)  e Remoção (2)
  • changes armazena as mudanças feitas na forma de um dicionário, onde cada chave  será o atributo alterado e terá uma lista com dois valores associado, o antigo e o novo. Ex:
    • {"ultima_troca_senha": ["2015-01-13", "2015-08-13"], "senha": ["1234567", "123456"]}
  • timestamp registrará o momento da mudança no formato datetime, yyyy-mm-dd h:i:s
  • actor_id armazena o id do usuário logado que fez a alteração. Caso haja em algum momento alteração do model registrado e o usuário não estiver logado, este atributo será nulo. Este campo faz referência ao model User utilizado pelo django, na tabela auth_user.
  • content_type_id armazena o id do content_type, fazendo referência ao model ContentType utilizado pelo django, na tabela django_content_type.
  • remote_addr registra o ip do usuário que fez a mudança
  • additional_data armazenará informações adicionais, sendo nulo no registro automático exemplificado acima.



Para mais infrmações, consulte a documentação: http://django-auditlog.readthedocs.org/en/latest/

hasta!

quarta-feira, 12 de agosto de 2015

Criando um middleware para controlar acesso ao site via IP


Para tal, criarei uma classe para gerenciar IPs permitidos via administrativo. Crie uma nova app no seu projeto e dê o nome de permissoes.

Deixe os arquivos como a seguir:

models.py

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

# Create your models here.
class IP(models.Model):
 """(IP description)"""
 ip = models.GenericIPAddressField()
 descricao = models.CharField(max_length=255, null=True, blank=True,verbose_name=u'Descrição')
 data = models.DateTimeField(auto_now=True)

 class Meta:
  verbose_name, verbose_name_plural = u"IP" , u"IPs"
  ordering = ('-data',)

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


admin.py

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

class IPAdmin(admin.ModelAdmin):
 search_fields = ('ip', 'descricao',)
 list_display = ('ip', 'descricao','data',)
 list_filter = ['data',]
 save_on_top = True

admin.site.register(IP, IPAdmin)


Adicione sua app no INSTALLED_APPS:

...
'permissoes',
...


E por fim, não menos importante, o middleware. Crie um arquivo chamado middleware.py e coloque no mesmo diretorio do settings.py com o conteúdo abaixo:

# coding: utf-8

from django.conf import settings
from django.http import HttpResponseRedirect

from permissoes.models import IP

class NeedToLoginMiddleware(object):
 def process_request(self, request):
                # pega o IP do usuário
  ip = request.META['REMOTE_ADDR']

                # verifica se o ip consta na base de ips permitidos
  ip_permitido = IP.objects.filter(ip=ip).count()

                # lista de urls que não serão validadas
  ALLOWED_URLS = [
   '%s' % settings.LOGIN_URL,
   '/admin/',
  ]

  for a in ALLOWED_URLS:
   try:
    if request.META['PATH_INFO'].find(a) >= 0:
     return None
   except:
    pass

                # verifica se o ip está permitido
  if ip_permitido:
   return None

  else:
                        # se o ip não estiver permitido, verifica se o usuário está autenticado 
                        # e redireciona para url de login em caso negativo.
   if not request.user.is_authenticated(): #if ip check failed, make authentication check
    return HttpResponseRedirect(settings.LOGIN_URL)


  return None

Adicone ele na sua setting MIDDLEWARE_CLASSES:

MIDDLEWARE_CLASSES = (
    ...
    'app.middleware.NeedToLoginMiddleware',
)

hasta!