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!