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!

sexta-feira, 7 de agosto de 2015

Autenticação sem o request.user.get_profile


Desde do Django 1.5, o framework possibilita a criação de um modelo de Usuário personalizado. Faço parte do time de pessoas que não concorda em adicionar campos não relacionados com a autenticação na classe de Usuário.

Para resolver esse problema, já que a partir da versão 1.7 do django este método não é mais recomendado, faremos algumas modificações nos arquivos.

Mudanças que tratrei aqui são fáceis de implementar e não irão gerar problemas com autenticação já existente e/ou migrações de banco.

Cenário: Imagine que temos uma app cadastros que possui uma classe Cadastro e esta tem um vínculo com a classe User do Django, para fazer uso do sistema de autenticação.

Remova a setting AUTH_PROFILE_MODULE:


Este passo é fácil. Encontre o AUTH_PROFILE_MODULE no seu arquivo settings.py e remova  linha.

# No seu settings.py:
AUTH_PROFILE_MODULE = 'cadastros.cadastro'     # remova esta linha


Mude seu modelo que possui o vínculo com a classe User do Django:


Antes:

from django.db import models
from django.contrib.auth.models import User

class Cadastro(models.Model):
   """modelo que representa informações adicionais sobre o usuário"""
   user = models.ForeignKey(User, unique=True)  # mude esta linha
   # ... outros campos aqui

Depois:

from django.db import models
from django.contrib.auth.models import User

class Cadastro(models.Model):
   """modelo que representa informações adicionais sobre o usuário"""
   user = models.OneToOneField(User) # nova linha
   # ... outros campos aqui


Aplique as migrações para concluir esta etapa:

python manage.py makemigrations cadastros
python manage.py migrate cadastros


Atualize seu código:


Antes:
cadastro = request.user.get_profile()

Depois:
cadastro = request.user.cadastro


hasta!