Posted about 1 hour ago
Quando eu comecei a usar o Django, eu criava uma página para "exibir mensagens do sistema ao usuário". É uma mania remanescente dos tempos de PHP que resolve bem a necessidade de mostrar coisas como "Senha modificada com
... [More]
sucesso!". Isso se prolongou por mêses, por uma maldita falta de atenção ao ler a documentação [1] do sistema de autenticação.
Pois é, o Django oferece o recurso de mensagens do sistema para fazer justamente isso: mostrar mensagens de aviso ao usuário.
Este sistema funciona como um pool: você lança quantas mensagens quiser em uma instância específica para isso e na primeira oportunidade, aquelas mensagens são despejadas para o usuário, limpa o pool e começa tudo novamente.
A forma mais comum de se mostrar mensagens é criando um espaço em seu template mais básico (aquele do qual todos são herdados) para mostrar essas mensagens. Vamos fazer e ver como funciona.
Registrando mensagens
Primeiramente, é preciso estar ciente de que as mensagens são registradas por usuário, ou seja, este recurso não está disponível para usuários anônimos (usuários que não logaram no sistema). Isso tem solução, que vou mostrar adiante, mas vamos nos atêr ao registro da mensagem.
A qualquer momento, tendo-se um usuário em mãos - normalmente a partir da variável request.user - basta escrever o seguinte código para registrar uma mensagem para aquele usuário:
usuario.message_set.create(message='Mensagem')Na prática, nós poderíamos fazer um código como o seguinte:
@login_required
def excluir(request, cliente_id):
try:
Cliente.objects.get(id=cliente_id).delete()
request.user.message_set.create(message=_('Cliente excluido com sucesso'))
except Cliente.DoesNotExist, e
request.user.message_set.create(message=_('Cliente nao encontrado'))
return HttpResponseRedirect('/clientes/')Este código acima tenta excluir um Cliente existente e exibe uma mensagem de sucesso após o feito, caso o Cliente citado não exista, uma mensagem de aviso é dada.
Mostrando as mensagens ao usuário
Agora, para mostrar as mensagens registradas a um usuário, basta utilizar-se da variável de contexto messages. Para que esta mensagem esteja disponível, é necessário que seu template seja tratado no contexto padrão, ou seja, caso tenha utilizado render_to_response, não se esqueça de colocar o sempre bem-vindo context_instance=RequestContext(request) na declaração. No mais, basta editar sua template mais básica (normalmente a que todos os templates herdam) com um código como este:
{% if messages %}
<ul class="mensagens">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}Esta lista (que de preferência deve ser tratada com um CSS adequado) irá exibir, sempre que existir, uma lista das mensagens registradas para aquele usuário até aquele momento. As mensages contidas nesta lista serão exibida apenas uma vez, já que elas são excluídas exatamente após a exibição. E uma outra sugestão sobre elas é que se crie um JavaScript com timeout, assim, você pode exibir as mensagens por um tempo definido e ocultá-las depois.
Mostrando mensagens para usuários anônimos
Usuários anônimos são aqueles que não logaram no sistema. Mensagens para esses usuários são necessárias, até mesmo para que esses se registrem. O Django não dispõe desse recurso, mas é possível resolver de algumas formas diferentes, como em [2].
A minha forma de resolver isso foi:
Criar uma variável de contexto com o nome 'messages' que substitui a variável padrão do sistema. Ela retorna as mensagens de uma variável de sessão criada com esse propósito caso o usuário não esteja autenticado;
Criar uma função para se registrar mensagens, que da mesma forma registra a mensagem para o usuário autenticado ou para a variável de sessão.
Veja como ficou:
context.py
from django.conf import settings
def context_processors(request):
ret = {}
if not request.user.is_authenticated() and request.session.get('anonymous_user_messages', []):
ret['messages'] = [x for x in request.session.get('anonymous_user_messages', [])]
request.session['anonymous_user_messages'] = []
return retEste módulo deve ser registrado na setting TEMPLATE_CONTEXT_PROCESSORS, assim por exemplo:
TEMPLATE_CONTEXT_PROCESSORS = (
...
'apps.utils.context.context_processors',
)Função para registro de mensagens
def post_message(request, message):
if request.user.is_authenticated():
request.user.message_set.create(message=message)
else:
request.session.setdefault('anonymous_user_messages', [])
request.session['anonymous_user_messages'].append(message)Agora nós mudamos o código visto lá no início para ficar da seguinte forma:
def excluir(request, cliente_id):
try:
Cliente.objects.get(id=cliente_id).delete()
post_message(request, _('Cliente excluido com sucesso'))
except Cliente.DoesNotExist, e
post_message(request, _('Cliente nao encontrado'))
return HttpResponseRedirect('/clientes/')Observações finais
Você deve ter notado o _(...) nas mensagens exibidas. Isso é feito para se aproveitar o recurso de internacionalização do Django. Você pode optar por remover esta parte da sintaxe ou fazer a seguinte importação:
from django.utils.translation import ugettext as _Links relacionados
[1]http://www.djangoproject.com/documentation/authentication/#messages
[2]http://www.djangosnippets.org/snippets/319/ [Less]
Posted about 1 hour ago
Passando rapidinho pra recomendar o DjangoPeople.net [1], site criado por Simon Willison [2] e Natalie Downe [3] para quem gosta de Django.
Meu perfil pode ser conferido em [4]. A lista de sites não inclui sistemas em produção
... [More]
:)
Links relacionados
[1]http://djangopeople.net/
[2]http://simonwillison.net/
[3]http://notes.natbat.net/
[4]http://djangopeople.net/marinho/ [Less]
Posted about 1 hour ago
Uma grande parte das dúvidas que chegam até mim no universo Django são relacionadas à metodologia ágil, especialmente sobre TDD (testes). A maior parte dos sintomas apontam ao caso clássico de rejeição ao ágil. É que muitas pessoas querem
... [More]
compreender o ágil, partindo de princípios criados por metodologias tradicionais, e isso é realmente, quase impossível.
É preciso tomar a pílula vermelha.
Mas o que isso significa? Significa que você deve pegar tudo o que você conhece sobre gerência de projetos e processos tradicionais aplicados a software, colocar em um latão de cobre devidamente documentado, soldar, e enterrar no CNEN [1] mais próximo.
Ok, se você não é goianiense, provavelmente não pegou o trocadilho, mas não importa, o que importa é o resultado ;)
É preciso romper com o tradicional. Não que PmBok, RUP, etc sejam totalmente errados, mas os casos onde o primeiro se aplica são relacionados à engenharia, e definitivamente, não há uma engenharia de software. Já o segundo é um baú cheio de coisas interessantes, mas que levam ao engano clássico de querer o pacote completo, e isso é realmente um problema.
Eu estou preparando algo mais interessante sobre TDD e Django com o propósito de ajudar melhorar a compreensão da união entre as duas ferramentas, mas enquanto isso não acontece, eu gostaria de sugerir alguns links (quase) obrigatórios para quem gosta do assunto (ou para quem quer passar a ter uma vida cor-de-rosas).
Seguem abaixo:
ImproveCast
Ótimo podcast liderado por Vinícius Teles com entrevistas e debates sobre o assunto. Ouça especialmente o 5, com o argentino Juan Bernabó;
http://www.improveit.com.br/podcast
Top 5 desculpas de quem não quer programar orientado a testes
Não preciso dizer mais nada... hehe
http://dojofloripa.wordpress.com/2006/11/03/top-5-desculpas-de-quem-nao-quer-programar-orientado-a-testes/
Testing Django Applications
Ponto obrigatório para djangonautas utilizando testes que esclarece alguns enganos comuns da comunidade
http://www.djangoproject.com/documentation/testing/
Introduction to Test Driven Design (TDD)
Ninguém menos que Scott Ambler apresentando o TDD
http://www.agiledata.org/essays/tdd.html
Wikipedia: Test-driven development
Texto introdutório na Wikipedia
http://en.wikipedia.org/wiki/Test-driven_development
Livros que introduziram o TDD (não, eu não li, mas são os mais importantes no assunto)
http://www.amazon.com/exec/obidos/ASIN/0321146530/ambysoftinc
http://www.amazon.com/exec/obidos/ASIN/0131016490/ambysoftinc
The three rules of TDD
"As três regras do TDD" - para deixar claro sobre as tentativas frustradas de se escrever um teste megalomaníaco (e cair nos mesmos erros tradicionais)
http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd
Scott Ambler: podcasts
Ouça esse cara. Literalmente
http://www.ambysoft.com/podcasts/
Martin Fowler: articles
Também importante para estar em seu leitor RSS
http://martinfowler.com/articles.html
e veja também meus links no del.icio.us relacionados ao assunto:
http://del.icio.us/marinho/extreme_programming
http://del.icio.us/marinho/tdd
http://del.icio.us/marinho/projetos
Bom, é isso aí. E boa sorte ;)
Links relacionados
[1]http://pt.wikipedia.org/wiki/CNEN [Less]
Posted about 1 hour ago
Bom, como devem ter notado, tenho estado ausente até mesmo nas conversas de Google Talk e na lista de discussão.
Também não é por menos, passo atualmente por três lançamentos de peso onde tenho a resposabilidade técnica de fazer
... [More]
acontecer. É prazeroso, mas ao final do dia tudo o que eu quero é a família, e assim tem sido.
A última grande novidade que tenho para vocês é o VivixVideo [1], um site bilíngue para agregação de vídeos totalmente baseado nos conceitos da web2.0: o usuário faz o site, em quase todas as formas da palavra, e os planos levam em consideração a amplitude dessa filosofia: teremos em breve uma área de desenvolvimento onde será possível publicar tickets e participar da construção do site até onde for possível essa interação.
A criação levou pouco mais de 2 meses, contando com uma equipe de 5 pessoas em seu tempo extra-expediente, mas com muita boa vontade, as previsões de prazo foram cumpridas com algumas gordurinhas a mais trasformada em recursos adicionais.
O lançamento conta com premiações e possibilidades de lucro por parte dos participantes, e os planos para novidades são ousados.
Django
A versão utilizada do Django é a 0.97-newforms-admin [2], a versão do Python é 2.5, com Apache, mod_python e Fedora Gnu/Linux.
Aplicações externas
Dentre os recursos extra utilizados, pelos quais somos gratos, destacam-se:
django-diario [3]
django-forum [4]
django-tagging [5]
django-ads [6]
django-microformats [7]
djapian [8]
django-plus [9]
ajax many2many widget [10]
jQuery [11]
jQuery auto-complete plugin [12]
yadsel [13]
Equipe
A equipe, que está parabéns pelos resultados é composta por:
Márcio [14]
Marcos Freitas [15]
Mariana Queiroz [16]
Marinho Brandão [17]
Miltinho Brandão [18]
Links relacionados
[1]http://vivixvideo.com/
[2]http://code.djangoproject.com/wiki/NewformsAdminBranch
[3]http://code.google.com/p/django-diario
[4]http://code.google.com/p/django-forum
[5]http://code.google.com/p/django-tagging
[6]http://code.google.com/p/django-ads
[7]http://code.google.com/p/django-microformats
[8]http://code.google.com/p/djapian
[9]http://code.google.com/p/django-plus
[10]http://django-plus.googlecode.com/files/ajax_m2m_widget.rar
[11]http://jquery.com
[12]http://code.google.com/p/jquery-autocomplete/
[13]http://code.google.com/p/yadsel
[14]http://vivixvideo.com/u/editor/
[15]http://vivixvideo.com/u/marcosfreitas/
[16]http://vivixvideo.com/u/Mariana/
[17]http://vivixvideo.com/u/marinho/
[18]http://vivixvideo.com/u/miltinho/ [Less]
Posted about 1 hour ago
Os campos ManyToManyField são uma beleza: permitem que se selecione diversos registros extrangeiros para um único campo, possibilitando campos clássicos onde normalmente seria necessária uma nova classe com tratamento e manutenção
... [More]
particulares.
No entanto, esse tipo de campo traz um problema consigo: é que o SelectMultiple [1] carrega todas as opções disponíveis para o usuário, o que no caso de grande quantidade de registros, cria uma imensa estrutura em HTML capaz de travar até o mais rápido dos navegadores.
O widget FilteredSelectMultiple [2] oferece alguma organização para isso, mas ainda assim, a carga é a mesma ou ainda maior.
O novo admin, do branch newforms-admin [4] oferece o ManyToManyRawIdWidget [3], muito leve, mas nada amigável, considerando que este exibe somente o código dos itens selecionados, o que não ajuda de forma alguma o usuário final.
Com um problema para resolver num trabalho onde nos aproximamos de uma base de 20 mil clientes, surgiu a idéia para o Ajax ManyToManyField Widget, um nome meio D. Pedro I, mas que traduz exatamente o que é: uma alternativa leve sem se tornar estranha ao usuário: exibe-se um campo de auto-complete em Ajax, e à medida que o usuário escolhe o item desejado, clica-se no íconde com sinal de " " e este é adicionado a uma lista logo acima.
Para isso, fiz uso de do plugin de auto-complete [5] disponível para jQuery [6], criado por Dylan Verheul. Com algumas modificações. Após uma conversa com o mesmo, eu soube que as minhas modificações se tornaram desnecessárias ante à nova versão disponível - farei a adaptação em breve.
Verifique os exemples disponíveis, e os arquivos estáticos utilizados. É necessário o jQuery, de preferência em sua versão mais recente.
Por fim, é importante ressaltar os dois elementos principais do widget:
auto_complete_view
Esta é uma generic view necessária para a disponibilização dos dados desejados em formato separado por pipe (|) - este é o formato utilizado pelo plugin de auto-complete - com possibilidade de pesquisa por quantos campos forem necessários:
from django.conf.urls.defaults import *
from apps.utils.ajax_m2m_widget import auto_complete_view
from models import Spare, Category
def spare_auto_query_func(q):
return Spare.objects.filter(name__startswith=q)
def category_auto_query_func(q):
return Category.objects.filter(name__startswith=q)
urlpatterns = patterns('apps.spares.views',
(r'^spare/auto/$', auto_complete_view, {'query_func': spare_auto_query_func, 'desc_field': 'name', 'id_field': 'id'}),
(r'^category/auto/$', auto_complete_view, {'query_func': category_auto_query_func, 'desc_field': 'name', 'id_field': 'id'}),
)AjaxMultiSelect
Este é o widget a ser utilizado no admin (que seja on branch newforms-admin) ou em algum form do padrão NewForms. Veja o exemplo abaixo no Admin do newforms-admin:
from django.contrib import admin
from django.contrib.admin.options import ModelAdmin
from apps.spares.models import Category, Spare
from apps.utils.ajax_m2m_widget import AjaxMultiSelect
class SpareAdmin(ModelAdmin):
list_display = ('name','manufacturer','description')
list_display_links = ('name',)
list_filter = ('manufacturer',)
raw_id_fields = ('manufacturer','specification_sheet',)
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'categories':
kwargs['widget'] = AjaxMultiSelect(db_field.verbose_name, Category, '/spares/category/auto/', attrs={'style': 'margin-left: 105px;'})
elif db_field.name == 'equivalents':
kwargs['widget'] = AjaxMultiSelect(db_field.verbose_name, Spare, '/spares/spare/auto/', attrs={'style': 'margin-left: 105px;'})
if 'widget' in kwargs:
return db_field.formfield(**kwargs)
else:
return super(SpareAdmin, self).formfield_for_dbfield(db_field, **kwargs)
admin.site.register(Category)
admin.site.register(Spare, SpareAdmin)Agora um exemplo em form:
from django import newforms as forms
from apps.spares.models import Category, Spare
from ajax_m2m_widget import AjaxMultiSelect
class MyForm(forms.Form):
spares = forms.Field(widget=AjaxMultiSelect(db_field.verbose_name, Spare, '/spares/spare/auto/'))
categories = forms.Field(widget=AjaxMultiSelect(db_field.verbose_name, Category, '/spares/category/auto/'))O download está disponível em [7] (o Google fez o favor de bloquear minha conta para criação de novos projetos pelas próximas 24 horas por motivos que só deus sabe).
Links relacionados
[1]http://code.djangoproject.com/browser/django/trunk/django/newforms/widgets.py#L245
[2]http://code.djangoproject.com/browser/django/branches/newforms-admin/django/contrib/admin/widgets.py#L12
[3]http://code.djangoproject.com/browser/django/branches/newforms-admin/django/contrib/admin/widgets.py#L112
[4]http://code.djangoproject.com/wiki/NewformsAdminBranch
[5]http://code.google.com/p/jquery-autocomplete/
[6]http:/jquery.com/
[7]http://django-plus.googlecode.com/files/ajax_m2m_widget.rar [Less]
Posted about 1 hour ago
Depois de um longo período apenas mantendo a estabilidade da versão atual, tirei ontem o dia para concluir os ajustes necessários para a versão 0.6 do Yadsel, com foco quase que exclusivo em projetos construídos com Django.
Ocorre que
... [More]
antes disso, o Yadsel trabalhava somente com versões de forma linear, ou seja: uma classe, uma versão.
Isso permaneceu até a versão 0.4, quando implementei o recurso de Extensible Versions (versões extensíveis) e Partial Versions (versões parciais). Essa versão inaugurou a idéia de poder ter várias classes em uma só versão. Melhorou na organização dos arquivos de versões e possibilitou trabalhar várias aplicações compartilhando uma mesma versão.
Mas ainda assim, outro recurso era necessário para o uso cotidiano no Django. No Django, as aplicações são independentes umas das outras, e ainda assim, podem haver eventuais dependências. Isso cria a necessidade de suporte a múltiplas versões em um mesmo projeto, ou seja: cada aplicação, uma versão. Para isso foi necessário implementar o Version Space.
Version Space
O Version Space é um recurso novo do Yadsel que torna possível que cada "espaço" no controle de versões possua sua própria versão, histórico e log. No caso do Django, o version space traduz-se pelo nome da aplicação, em outros aplicações, o desenvolvedor pode implementar como bem quiser.
Em outras palavras: eu posso manter uma aplicação de newsletter em diversos projetos com evolução de versões independentes do restante, incluindo toda a parafernália de DDL e DML que o Yadsel suporta: Tabelas, Domínios, Triggers, Procedures, Views, etc.
Yadsel no Admin
No embalo do Version Space veio o suporte ao Admin. É simples, ao instalar a aplicação 'yadsel.drivers.django_app' em seu INSTALLED_APPS, seu Admin passará a exibir duas seções para visualizar logs e históricos da evolução do banco.
Yadsel pelo manage.py
O Yadsel implementa um novo comando ao manage.py: yadseltool. A lógica de funcionamento é a mesma da yadseltool independente, porém seguindo a sintaxe de comandos baseados aplicações do manage.py:
$ python manage.py help yadseltool
Usage: manage.py yadseltool [options] [appname ...]
Executes Yadsel database version control for the given app name(s).
Options:
--settings=SETTINGS The Python path to a settings module, e.g.
"myproject.settings.main". If this isn't provided, the
DJANGO_SETTINGS_MODULE environment variable will be
used.
--pythonpath=PYTHONPATH
A directory to add to the Python path, e.g.
"/home/djangoprojects/myproject".
--traceback Print traceback on exception
--action=ACTION Action of evolution; up=upgrade, down=downgrade
--from=FROM
--to=TO
--mode=MODE Mode of output; hidden=messages are hidden, steps=step
by step, interactive=confirms actions, output=only
prints to output
--test=TEST Set test mode
--history=HISTORY Write history of versions
--silent=SILENT Keeps exception messages
--log=LOG Write a log of changes
--version show program's version number and exit
-h, --help show this help message and exitExemplo de chamada do yadseltool
$ python manage.py yadseltool minha_aplicacaoMas como afinal utilizar o Yadsel em meu projeto Django?
Bom, primeiro é necessário baixar a versão 0.6 do Yadsel em sua máquina
http://yadsel.googlecode.com/files/yadsel-0.6-with-django-support.tar.gz
Feito o download, instale o Yadsel numa versão 2.4 ou superior do Python
$ python setup.py installEm seu projeto, acrescente a seguinte aplicação à setting INSTALLED_APPS do arquivo settings.py
'yadsel.drivers.django_app'
Rode o syndcb
$ python manage.py syncdbSerão criadas as duas tabelas necessárias para o histórico e log do Yadsel. Entenda que 'histórico' trata-se do controle das versões, é ele que sabe se sua aplicação está na versão X ou Y. O 'log' trata-se de cada comando SQL que é gerado e executado, assim como suas respectivas eventuais mensagens de erro.
Crie a seguinte estrutura de pastas em uma de suas aplicações:
minha_aplicacao/
minha_aplicacao/yadsel_versions/
minha_aplicacao/yadsel_versions/__init__.py
minha_aplicacao/yadsel_versions/versao1.py
minha_aplicacao/yadsel_versions/versao2.py
minha_aplicacao/yadsel_versions/versaoN.pyo arquivo __init__.py deve conter ou importar as classes de versões, que podem ser estruturadas como bem quiser, seja em módulos ou em pacotes.
O conteúdo provável do arquivo __init__.py será este:
from versao1 import *E o do arquivo versao1.py (ou qualquer outro que contenha classes de versões) será provavelmente este:
from yadsel.core import *
class Version1(Version):
version_number = 1
def up(self):
pass
def down(self):
passou como este exemplo:
from yadsel.core import *
class MinhaVersao1(Version):
version_number = 1
def up(self):
CreateTable('minha_tabela_nova',
id = Integer(primary=True)
name = Varchar(50, required=True),
).append_to(self)
AlterTable('states',
Add('percent', Decimal(15, 5, default=0)),
).append_to(self)
ExecuteSQL("""
Create or Alter Procedure sp_teste...
""").append_to(self)
def down(self):
ExecuteSQL("""
Drop Procedure sp_teste
""").append_to(self)
AlterTable('states',
DropColumn('percent'),
).append_to(self)
DropTable('minha_tabela_nova').append_to(self)A minha sugestão é que se use o Yadsel no Django somente para manter a evolução, não para criar tabelas em si. Isso porque o Django o já cria as tabelas muito bem. O que ele não faz é o resto :D [Less]
Posted about 1 hour ago
O Ari Caldeira anunciou a seguinte oportunidade de trabalho para Sorocaba, interior paulista:
Olá!
Estamos procurando por um programador com conhecimentos em Python e Django, para trabalho em Sorocaba, interior de São
... [More]
Paulo.
Por favor, entrem em contato particular pelo emeio ari@tauga.com.br.
Obrigado!
Ari Caldeira
Ta?ga Educação e Tecnologia - www.Tauga.com.br
Fone/Fax: 55 (15) 3388-7305
Emeio: ari arroba tauga.com.br [Less]
Posted about 1 hour ago
O Andre Henrique anunciou uma vaga para desenvolvedor Python, com desejável conhecimento de Django para Itu, interior paulista:
Empresa em fase inicial, a atuação da ALTAVIX Digital é voltada ao segmento de Tecnologia da Informação
... [More]
, com foco em sistemas de operação e controle da distribuição de imagem e vídeo. Localiza-se no centro da cidade de Itu, interior de SP.
Sobre a vaga:
System Message: WARNING/2 (<string>, line 6)
Bullet list ends without a blank line; unexpected unindent.
Desenvolvimento de sistema web para comunicação digital.
Conhecimentos desejáveis: Linux, SQL, Django, e Javascript.
Contratação por prestação de serviço, mas com o objetivo de integração à equipe fixa.
Sobre o candidato:
System Message: WARNING/2 (<string>, line 11)
Bullet list ends without a blank line; unexpected unindent.
O perfil que buscamos para os integrantes da nossa equipe é aquele que reúna características para o constante crescimento de competências, entre as quais destacamos: iniciativa, dinamismo, estudo, inovação e comprometimento.
Encaminhar currículo para rh_cv em altavix ponto com [Less]
Posted about 1 hour ago
Dois meses.
É tempo demais para ficar sem publicar nenhum artigo sequer.
Mas foi um tempo que passou rápido demais, eu francamente não havia percebido que se foram 2 meses desde o último artigo.
Fazendo um resumo geral
... [More]
desse tempo, basicamente trabalhei em minhas tarefas diárias na WorldNews [1] e no VivixVideo [2], tive muitas horas divertidas com a Tarsila [3] e alguma dedicação em projetos livres (mais detalhes mais adiante). Também estive mais envolvido que o normal no mercado financeiro, agora operando como day trader, e você sabe: vivemos dias atribulados na bolsa, então toda a atenção é necessária.
Mas enfim, quero mesmo é falar sobre o que trabalhei nas últimas semanas que está disponível para desenvolvedores Python que utilizam Django.
django-dynamic
Ainda engatinhando. É um projeto que visa no longo prazo oferecer uma alternativa amigável para publicação e manutenção de sites em Django. Já conta com gerenciador de configurações diversas para o site (entre elas o recurso de colocar o site em manutenção através do Admin).
Conta também com um SQL Shell e um File Manager, respectivamente para gerenciar o banco de dados e os arquivos do site.
Veja mais detalhes em [4]
django-antivirus
Projeto que fiz ontem.
Trata-se da integração do Django com antivirus. Neste momento somente o ClamAV é suportado. Em resumo a aplicação oferece uma classe de modelo para se registrar arquivos e solicitar sua verificação por virus.
Isso pode ser feito facilmente por um template filter, que se resume a informar o objeto e o nome do campo onde está informado o arquivo. O retorno desse filtro é uma requisição Ajax que verifica o arquivo por virus e exibe um link para download. Caso um virus seja encontrado, é exibida uma mensagem com essa informação e o arquivo é verificado somente uma vez, portanto, na próxima vez que alguém requisita aquela página ela exibe a informação armazenada.
Mais detalhes em [5]
django-ads
Aplicação que levou uns 10 dias de trabalho. Em poucas palavras, faz o que o Google AdWords faz - guardadas as devidas proporções. Também tem um recurso interessante para a publicação rotativa ou baseada em regras para banners, propagandas e outros programas de afiliados. Simples de usar e bastante poderoso.
Mais detalhes em [6]
SectionedForm
Não sei se o nome ficou bom, mas o código me ajudou bastante.
Sabemos que o Admin permite se dividir o form em seções, através dos fieldsets. Mas isso não é suportado pelo NewForms.
Pois bem, esse snippet é pra isso. Você herda seu form do SectionedForm and declara os atributos:
sections = (
(None, ('name','age','date')),
(_('Last Employment'), ('name','date','position','location_country','location_city')),
)Ele irá criar um subtitulo "Last Employment" e colocar os campos listados abaixo ;)
Mais detalhes em [7]
Bom, é isso aí. Outros snippets interessante que fiz podem ser encontrados em [8]. No mais agora é aguardar ansiosamente pela PyCon Rio [9] e pela versão 1.0.
Links relacionados
[1]http://wn.com/
[2]http://vivixvideo.com/
[3]http://www.flickr.com/photos/marinho/1173837495/in/set-72157594192378034/
[4]http://code.google.com/p/django-dynamic/
[5]http://code.google.com/p/django-antivirus/
[6]http://code.google.com/p/django-ads/
[7]http://www.djangosnippets.org/snippets/798/
[8]http://www.djangosnippets.org/users/marinho/
[9]http://pyconbrasil.com.br/ [Less]
Posted about 1 hour ago
Resolvi publicar este como um artigo separado. Por ser maior e mais importante.
No momento trata-se apenas de um patch em avaliação pelo core team. Se for adotada pelo framework, dificilmente será na versão 1.0, prevista para setembro
... [More]
[1].
A criação desse método, que depende de ajuste direto no código-fonte do framework, surgiu da necessidade de criar uma solução escalável para requisições redundantes ao banco de dados, especialmente as concorrentes.
Em resumo, quando utilizado encadeado a uma chamada de QuerySet [2], o método armazena o resultado do banco em cache e na próxima vez que aquela requisição for feita, ela será feita ao cache antes de chegar até o banco. O ganho de performance é notável, mas a escalabilidade ficou tremenda. O cache backend deve ser de preferência de memória, como o memcached [3] ou locmem [4]).
Seu uso pode ser bastante amplo e deve-se tomar o cuidado de respeitar o timeout e de usá-lo somente em requisições onde se tem uma visão clara quanto a essa chamada depender de atualizações constantes no banco de dados. Minha intuição é de que 90% das requisições não terão alterações relevantes em menos de 60 segundos, mas isso vai varia de acordo com a realidade do site ou sistema.
Usos mais prováveis do método:
Dentro de uma QuerySet declarada
class CategoriaManager(models.Manager):
def get_query_set(self):
q = super(CategoriaManager, self).get_query_set()
return q.cache(300)Observe que este código faz com que TODAS as requisições a banco feitos através deste manager sejam feitas através do recurso de cache. Isso é perigoso, viu?
Encadeado a uma QuerySet individual
lista = Video.all().cache(300)Note que agora o uso do cache se limitou a uma chamada, um tanto genérica.
Moral da história: se você tem 1 usuário requisitando uma lista de Video, ela será armazenada por 300 segundos. Digamos que nesses próximos 300 segundos você tenha 100 requisições idênticas ao banco de dados (mesmo que partam de partes diferentes do código), todas essas requisições serão alimentadas pelo cache (e você sabe: memória é mais veloz que disco rígido). Quanto mais requisições simultâneas, mais robusto o sistema fica. Isso se chama escalabilidade. E claro, performance.
A idéia ficou tão legal, que já está fazendo influência, como o snippet [6].
Mais detalhes em [5]
Links relacionados
[1]http://code.djangoproject.com/wiki/VersionOneRoadmap
[2]http://www.djangoproject.com/documentation/db-api/#queryset-methods-that-return-new-querysets
[3]http://www.djangoproject.com/documentation/cache/#memcached
[4]http://www.djangoproject.com/documentation/cache/#local-memory-caching
[5]http://code.djangoproject.com/ticket/7338
[6]http://www.djangosnippets.org/snippets/815/ [Less]
Posted about 1 hour ago
Marinho: well, as I said, I'm brazilian, and Brazil has already many djangonauts many people using for corporate solutions and some big companies starting to use in some startup projects... the community is growing and we very happy :) so, my
... [More]
questions are related to our reality
and first at all, I wish to let you know that my english is not the best, so, be patient with me :)
Jacob: Your English is fine, no worries!
It's so cool hearing about Django getting used around the world; makes me really happy.
Marinho: I see :) you replied to me with a nice availability, you seem to be a guy with good skills of leadership. Along the Django's lifetime, did you have some situation about people relations that dare you?
I mean: some situation when you lost the patience or bad discussion...
Jacob: Yeah, it's certainly happened!
Marinho: the recent suprises (for us) like DjangoCon and Django Foundation was some of those dares?
Jacob: We try really hard to maintain a friendly (or at least polite) tone, but... sometimes there are people who are just really difficult to deal with.
Marinho: I know a bit of this, and leading a great community is not an easy job
Jacob: There's a great talk that Ben Collins-Sussman and Brian Fitzpatrick give called "How Open Source Projects Survive Poisonous People": http://video.google.com/videoplay?docid=-4216011961522818645
Full of good advice -- the lessons they give there have saved by butt a few times.
Marinho: cool, I will spare some time to watch it
about the Django Foundation: What can we wait from it for the next years? What kind of acts?
Jacob: We haven't really thought all that far in advance, honestly. Our main goal is to have an "official" body that can represent Django and help out financially. Like most things around Django, we'll wait to see what the community wants us to do.
In the short term, we'll be sponsoring developer sprints and meetups to help get Django 1.0 out on time.
Marinho: Can we wait for certification or something like this?
Jacob: Do you mean some sort of official Django "developer certification"?
Marinho: yep
Jacob: We don't have any plans for that right now... it doesn't seem like something we'd want to get involved in, but as I said before if it's something the community really wants, we'd of course try to help out.
Marinho: Python community doesn't like certifications, but many companies like... is something that they feels more safe
understood
Jacob: Yeah, I understand... it's just something that's not very common in Open Source in general; not sure how it'd work.
Marinho: yes, I agree
and about the DjangoCon, what more details can we have about? have you already the date?
Jacob: We're just finalizing some details, and should be able to announce them maybe today, maybe Monday.
Marinho: great :)
about the ORM and new features...
Have you some idea about when could we have support to OODBMS, especially BigTable, Amazon SimpleDB and ZoDB? How much hard could this to do?
Jacob: This is actually something I've been thinking a lot about lately -- there's all of a sudden a bunch of these non-relational databases coming out, it would be awesome to support them.
I think it's actually not all that hard, though you have to remember that none of these DBs really support joins or other relational features so you could never really just switch DATABASE_ENGINE on the fly and expect your app to work.
Marinho: yes, this is what I was thinking about
And about MySQL master/slave support and multiple database connections?
Jacob: I'm not all that familiar with MySQL, so I'm probably the wrong person to ask there. As for multiple connections, there's some good work being done in that area; it won't make it into 1.0 because of timing, but hopefully we'll get it in soon.
Marinho: I see... this question is because most of great sites, like Flickr, YouTube, Slashdot and others use to adopt storing data in many (and sometimes randomic) database servers
Now about the Django usage and the future...
One or two years ago, thought you about Django being used as framework to create corporate softwares like ERPs, BIs, CRMs, Billing and other software for use in companies, especially not related to content management?
Jacob: When we first released Django, we never expected it to be as widely-used as it is. We though that maybe a few other online publishers would be interested, and perhaps we'd get some traction within the Python web community. We never thought that Django would get used in so many places (social networking, corporate/enterprise software, online shopping...)
Marinho: and now how do you feel when see that day-by-day more people use it in totally different kinds of solutions? Surprised? :)
Jacob: Surprised, yeah, but mostly just happy. It's really great, and all the disparate uses ensure that Django really works well.
That is, having all these people with different needs ensures that we don't get stuck in some particular niche.
Marinho: yes, is true... I think you feel so proud when knows that great players like Google and Yandex are using it :)
Jacob: Exactly -- it's really nice.
Marinho: What do you consider will be the great challenges for the framework in the next years?
Jacob: I think our greatest challenge is dealing with our own success. We delayed far to long getting 1.0 out, mostly because we didn't realize quite how large we'd gotten -- with a small community, you can afford to be a lot more sloppy. Now that we're really a big-time project we need to formalize our workflow more: regular, scheduled releases, more explicit division and delegation of duties, etc.
We also need to be ready for the inevitable backlash when it comes. As a large project you're inevitably going to eventually have people who just hate you, and dealing with the downside of success can be nasty. I've watched carefully how the Rails community handled the "Rails doesn't scale!" and "Rails is insecure!" stuff over the last year; they really did a great job listening to the rational voices and ignoring the people there just for kicks.
We'll certainly have "controversies" like that in the future; we need to make sure we handle them professionally.
Marinho: I understand, now Django lives a new reallity, diferent than before
Jacob: Exactly. We have to pay attention to how our community evolves and evolve our leadership to meet them.
Marinho: yes, is a truth... well, now going to the end: what more could you say to us? Something hot? Some newness that we don't know? :)
Jacob: Hmm... we do most things out in public -- the foundation and the conference are really the only two exceptions -- so I don't think there's anything left that's "secret" now. The next big thing for us is that Django 1.0 Alpha will be coming out in just over a week -- that'll be a great milestone, and we'll need lots of help testing it so that the final 1.0 release can be as good as possible.
Marinho: o.O great, this is a great newness for the community :)
well, I thank you for all, and congratule you for the success and for this nice talk, I'm a proud and thankful djangonaut and want to help ever I can
Jacob: Thank you!
Let me know when you post this; I'd love to read it.
Marinho: I will publish this maybe monday or tuesday in my blog or in the DjangoBrasil blog, I'm not sure about this, but I will try to publish early I can :)
yes, I will
have a good afternoon
Jacob: Thanks; you too. [Less]
Posted about 1 hour ago
Esse é um cara que eu admiro pela simplicidade e capacidade de liderança que demonstra a cada dia onde eu tenho a oportunidade de acompanhar.
Esta é a minha primeira entrevista e eu queria tirar minhas principais dúvidas envolvendo
... [More]
necessidades de grandes portais e sistemas corporativos, além de saber um pouco mais sobre as novidades mais quentes dos últimos mêses: Django Foundation, DjangoCon e versão 1.0.
Segue abaixo a gostosa conversa que tive com Jacob Kaplan-Moss, que poderia ter se extendido por mais um bom tempo, se não fosse a necessidade de parar por aí :)
Espero que gostem.
Marinho: bom, como eu disse, sou brasileiro, e o Brasil já tem muitos
djangonautas, muita gente usando (o Django) para soluções corporativas e algumas grandes
empresas começando a usá-lo em alguns projetos em fase inicial... a comunidade
está crescendo e nós muito felizes :) então, minhas perguntas são relacionadas
à nossa realidade.
e antes de tudo, eu gostaria de deixar você sabendo que meu inglês não é dos
melhores, então, tenha paciência comigo :)
Jacob: Seu inglês está legal, não se preocupe!
É muito legal ouvir sobre o Django sendo usado ao redor do mundo; me faz
realmente feliz.
Marinho: Eu noto :) você me respondeu com uma disponibilidade bacana, você
parece ser um cara com boas habilidades em liderança. Ao longo da história do
Django, você teve alguma situação nas relações pessoais que te desafiou?
Digo: uma situação quando você perdeu a paciência ou uma discussão ruim...
Jacob: Yeah, certamente aconteceu!
Marinho: as recentes surpresas (para nós) como a DjangoCon [5] e a Django
Foundation [4] tiveram algum desses desafios?
Jacob: Nós tentamos realmente manter um tom amigável (ou ao menos educado),
mas... às vezes há pessoas que são realmente difíceis de se lidar.
Marinho: Eu sei um pouco disso, e liderar uma grande comunidade não é
uma tarefa fácil
Jacob: Há uma ótima palestra que Ben Collins-Sussman [1] e Brian Fitzpatrick [2]
dão, chamada "How Open Source Projects Survive Poisonous People" ("Como
Projetos de Código Aberto Sobrevivem a Pessoas Diabólicas") [3] cheio de
bons conselhos -- as lições que eles dão têm salvado meu traseiro algumas
vezes.
Marinho: legal, eu vou tirar um tempo pra assistí-lo...
agora sobre a Django Foundation [4]:
O que podemos esperar dela para os próximos anos? Que tipos de ações?
Jacob: Nós realmente não pensamos que haverá um grande avanço, honestamente. Nossa
principal meta é ter um corpo "oficial" que pode representar o Django e ajudar
financeiramente. Como a maioria das coisas em torno do Django, nós vamos esperar para
ver o que a comunidade quer que façamos.
A grosso modo, nós vamos bancar os sprints de desenvolvedores e reuniões para ajudar o
Django 1.0 a sair no tempo (certo).
Marinho: Podemos esperar por uma certificação ou algo semelhante?
Jacob: Você quer dizer algum tipo de "certificação para desenvolvedor" oficial do
Django?
Marinho: sim
Jacob: Nós não temos planos para isso por agora... não parece ser algo que nós
queremos nos envolver, mas como eu disse antes, se isso for algo que a comunidade
realmente quer, é claro que vamos tentar ajudar.
Marinho: A comunidade de Python não gosta de certificações, mas muitas empresas
gostam... é algo que as faz se sentir mais seguras
Jacob: Yeah, eu entendo... é só algo que não é muito comum no Open Source em
geral; não estou certo de como isso funciona.
Marinho: sim, eu concordo.
e sobre a DjangoCon [5], que mais detalhes podemos ter a respeito? Vocês já tem
alguma data?
Jacob: Nós estamos só finalizando alguns detalhes, e devo estar pronto pra
anunciá-los talvez hoje, talvez na segunda.
Marinho: ótimo :)
agora sobre o ORM e novas funções...
Você tem alguma idéia de quando poderemos ter suporte a SGBDOO (bancos de dados
orientados a objetos), especialmente BigTable, Amazon SimpleDB e ZoDB? Quão
difícil isso seria pra fazer?
Jacob: Isso realmente é algo que eu venho pensando muito ultimamente --
repentinamente há todo um punhado desses bancos de dados não-relacionais saindo,
seria fascinante suportá-los.
Eu penso realmente que nem tudo seria aquela dificuldade, no entanto você deve
se lembrar que nenhum desses DBs realmente suportam joins ou outras características
relacionais, então você nunca poderia mesmo só mudar o DATABASE_ENGINE da mesma
forma e esperar que sua aplicação funcione.
Marinho: sim, isso é o que eu estava pensando...
E sobre suporte (aos recursos de) master/slave [6] do MySQL e conexões com múltiplos
bancos de dados?
Jacob: Eu não sou de todo familiar com o MySQL, então eu provavelmente sou a
pessoa errada a se perguntar sobre ele. Para múltiplas conexões, há um bom
trabalho sendo feito na área; ele não estará na (versão) 1.0 por causa do tempo,
mas estou esperançoso de que nós teremos em breve.
Marinho: Entendi... essa (minha) pergunta é porque a maioria dos grandes sites,
como o Flickr, YouTube, Slashdot e outros costumam adotar o armazenamento de dados em
muitos (e às vezes aleatórios) servidores de banco de dados.
Agora sobre o uso do Django e o futuro...
Um ou dois anos atrás, você pensava sobre o Django sendo usado como framework para se
criar softwares corporativos como ERPs, BIs, CRMs, Billing (Vendas) e outros softwares
para uso em empresas, especialmente não relacionados ao gerenciamento de conteúdo?
Jacob: Quando nós lançamentos a primeira release do Django, nós nunca esperávamos
que ela seria largamente usado como é (hoje). Nós pensamos que talvez alguns outros
publicadores online se interessariam, e talvez nós pegássemos alguma relevância na
comunidade (que usa) Python na web. Nós nunca pensamos que o Django seria usado em
muitos lugares (redes sociais, software corporativo/empresarial, vendas online...)
Marinho: e agora como você sente quando vê que dia-após-dia mais pessoas usam-no
em tipos totalmente diferentes de soluções? Surpreso? :)
Jacob: Surpreso, sim, mas principalmente feliz. É realmente ótimo, e todos os
diferentes usos confirmam que o Django realmente funciona bem.
Isto é, tendo todas essas pessoas com diferentes necessidades confirmando que nós
não nos prendemos a um nicho em particular.
Marinho: sim, é verdade... Eu acho que você se sente orgulhoso quando sabe que
grandes players como Google e Yandex [7] estão usando-o :)
Jacob: Exatamente -- é realmente bacana.
Marinho: O que você considera que serão os grandes desafios para o framework nos
próximos anos?
Jacob: Eu acho que nosso grande desafio é lidar com nosso próprio sucesso. Nós
aguardamos muito tempo para soltar a 1.0, principalmente porque nós não compreendemos
quão grandes nós ficamos -- com uma pequena comunidade, você pode arriscar a se sujar.
Agora que nós somos realmente um projeto de grandes proporções, nós
precisamos formalizar mais o nosso fluxo de trabalho: versões planejadas e regulares,
divisão mais explícita e delegação de responsabilidades, etc.
Nós também precisamos estar dispostos para uma inevitável oposição quando ela vier.
Como um grande projeto você inevitavelmente terá eventualmente pessoas que
simplismente te odeiam, e lidar com o lado baixo do sucesso pode ser desagradável.
Eu tenho acompanhado com cuidado como a comunidade de Rails segurou a carga de "Rails
não escala!" e "Rails não é seguro!" neste último ano; eles realmente fizeram um grande
trabalho ouvindo as vozes racionais e ignorando as pessoas que estavam só para
contrariar.
Nós vamos certamente ter "controvérsias" como essas no futuro; nós precisamos de ter
certeza que nós podemos segurar isso profissionalmente.
Marinho: Eu entendo, agora o Django vive uma nova realidade, diferente de antes
Jacob: Exatamente. Nós temos que prestar atenção em como nossa comunidade se
desenvolve e aceita nossa liderança ao seu encontro.
Marinho: sim, é verdade... bom, indo para o fim: o que mais você pode nos dizer?
Algo quente? Alguma novidade que não sabemos?
Jacob: Hmm... nós fazemos a maioria das coisas publicamente -- a fundação e a
conferência são realmente as únicas duas exceções -- então eu não acho que há algo
"em segredo" por agora. A próxima grande coisa para nós é o Django 1.0 Alpha que
está saindo em uma semana -- que será um grande marco, e nós precisaremos de
grande ajuda para testá-la até que a (versão) 1.0 final esteja melhor possível.
Marinho: o.O ótimo, essa é uma grande novidade para a comunidade :)
bom, eu agradeço por tudo, lhe parabenizo pelo sucesso e pela boa conversa, eu sou
um orgulhoso e grato djangonauta e quero ajudar sempre que puder.
Jacob: Obrigado
Links relacionados
[1]http://www.red-bean.com/sussman/
[2]http://www.red-bean.com/fitz/
[3]http://vivixvideo.com/videos/ource-projects-survive-poisonous-people-and-you-can-too/
[4](1, 2) http://www.djangoproject.com/foundation/
[5](1, 2) http://www.ericholscher.com/blog/2008/jul/7/djangocon-2008/
[6]http://dev.mysql.com/doc/refman/5.1/en/connector-j-reference-replication-connection.html
[7]http://kuda.yandex.ru/ [Less]
Posted about 1 hour ago
Já faz alguns meses que eu queria fazer alguns testes com o mod_wsgi, a alternativa ao mod_python que vem sendo cada vez mais comentada e sugerida na comunidade Django.
Ontem eu aproveitei o dia tranquilo pra unir o útil ao agradável e
... [More]
colocar isso em prática.
O que é WSGI?
Vamos "começar do começo": WSGI é mais simples do que parece, trata-se de uma interface definida pela PEP 333 [1] para intermediar a comunicação entre servidores web e frameworks Python. Ela surgiu pela necessidade qualquer um de nós nota com um pouco tempo: Python tem quilos de frameworksmuitas delas excelentes, e às vezes o mod_python parece ser um ornitorrinco em um ninho de coelhos quando implantamos alguns sites. É esquisito.
Mudando para o mod_wsgi
Para usar o WSGI puramente, é bastante simples, vamos fazer um pequeno script para ilustrar
def application(environ, start_response):
status_code = '200 OK'
headers = [('Content-Type', 'text/html')]
start_response(status_code, headers)
return ['So <b>testando</b>!']
from paste import httpserver
httpserver.serve(application, port='8000')Ao executar este script minúsculo e carregar em seu navegador a URL "http://localhost:8000", será exibido "So testando". Simples não? Veja
$ python my_app.py
serving on http://127.0.0.1:8000
No Django, a coisa exige só um pouco mais de detalhes, mas nada complexo. Como já explanado há poucos dias pelo Eric, da Metaphormedia, em [2], é recomendável que se crie dentro de seu projeto uma pasta "apache" com um script dentro chamado "django.wsgi" - estes nomes são escolha sua, mas eles têm sido usados por mais de uma referência. Com o conteúdo abaixo (modificado segundo as suas necessidades)
#!/usr/bin/env python
import os, sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) '/../'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()E configure seu apache, removendo o código que implementa o mod_python e adicionando o seguinte
WSGIScriptAlias / /caminho/do/seu/projeto/apache/django.wsgiAgora, para funcionar, é preciso que seu sistema operacional tenha o mod_wsgi instalado. No Ubuntu/Debian você instala com o seguinte comando
$ sudo apt-get install libapache2-mod-wsgiPronto, ao reiniciar o Apache, seu projeto estará lá, bonitão, rodando através do WSGI :)
Porque mod_wsgi?
Bom, essa deve ser a pergunta que você deve estar se fazendo. Se o mod_python sempre foi a alternativa recomendada e funciona bem, porque mudar para o mod_wsgi?
O mod_wsgi tem se comportado mais escalável que o mod_python. Por escalabilidade, entende-se a capacidade de um site de suportar o crescimento de uso sem perder a performance ou travar.
Ao ajustar um servidor para usar o mod_wsgi, pude notar essas diferenças. Elas não são tão grandes que se possa perceber com um simples teste com Apache Bench. É necessário forçar para notar a diferença, que tem sido até 10% superior.
Esse servidor que usei para fazer esses testes é uma VM (em Xen) com 512MB de RAM, rodando em um amd64, numa rede bastante estável, em Londres, enquanto que o Apache Bench foi executado em minha máquina.
Veja abaixo como os dois modos se comportaram:
Para compreender a legenda, o número após o "n" trata-se da quantidade de requisições enviadas e o número após o "c" trata-se da quantidade de requisições concorrentes, ou seja, "n1000c5" equivale a "1000 requisições enviadas de 5 em 5).
Tempo de resposta por requisição
Requisições por segundo
Load de 1 minuto
Load de 5 minutos
RSS / Memória real ocupada
Percentual da CPU
Conclusões
Todos os números apresentados acima não podem ser tomados como base exata para comparação, por alguns motivos, como a influência que o teste anterior efetua no subsequente, a oscilação da internet e a influência de outros processos.
Eu fiz questão de usar um servidor em produção onde está sendo servido um outro sistema da empresa, que mesmo que não estivesse em seu horário de pico, era usado normalmente por alguns usuários.
Há ainda toda a questão de controvérsias em torno do uso real da CPU e da memória.
Mas dá pra notar uma realidade clara: o mod_swgi se mostra mais vantajoso em memória e tempo de resposta. Não esqueçamos que 200 requisições concorrentes para um VPS com 512MB de RAM é uma senhora carga, equivalente a alguns milhões de pageviews por mês.
No teste de 2000 requisições a 200 concorrentes, ficou visível que a configuração de MaxRequestsPerChild (de 1000) entrou em cena e fez diferença no desempenho, tanto para segurar a carga quando para mudar um pouco a evolução do desempenho.
Por fim, devo avisar que no teste seguinte, de 5000 requisições a 200 concorrentes, o Apache caiu antes de completar 3000, em 3 tentativas.
Mais detalhes sobre o WSGI podem ser encontrados em [4], [5] e [6]
Links relacionados
[1]http://www.python.org/dev/peps/pep-0333/
[2]http://www.ericholscher.com/blog/2008/jul/8/setting-django-and-mod_wsgi/
[3]http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango
[4]http://www.slideshare.net/hdiogenes/wsgi-a-resposta-para-a-questo-definitiva-sobre-python-a-web-e-tudo-mais-368429?src=embed
[5]http://en.wikipedia.org/wiki/Wsgi
[6]http://www.wsgi.org/wsgi/
[7]http://del.icio.us/marinho/wsgi [Less]
Posted about 1 hour ago
Que tal aprender uma nova linguagem enquanto faz uma boa tarefa para melhorar a escalabilidade do banco de dados?
Bom, foi isso que eu fiz hoje durante quase todo o dia. Descobri no início do dia que Lua é a linguagem de script do MySQL
... [More]
Proxy e mergulhei pra fazer duas coisas bastante interessantes: Load Balancing e Cache de resultados.
Load Balancing
Eu conheço 3 formas de fazer load balancing com MySQL:
MySQL Cluster [1]
MySQL Proxy [2]
MySQL Master/Slave [3]
Mas na verdade não conhecia em profundidade nenhum deles, apenas uma idéia superficial.
No final de semana testei o Master/Slave. Parece ser o mais poderoso dentre os três métodos, mas usá-lo no Django significa mexer em boa parte do código do ORM. Não é a minha intenção. Cheguei a construir um backend pra isso, em cima do backend do MySQL, e até descobri que havia outro semelhante [4]. Mas nem o meu backend improvisado, nem o do Ivan Sagalaev me seduziram: muito limitado e cheio de falhas de design.
Pois bem, o MySQL Cluster também me desanimou depois que eu li diversos depoimentos contrários [5].
Sobrou o MySQL Proxy.
MySQL Proxy
Este é um software um tanto recente da MySQL, que basicamente faz a ponte entre o(s) servidor(es) de MySQL e a sua aplicação. É vantajoso pois é independente de linguagem ou framework: você desenha as regras e o que vem depois delas pouco importa, funciona da mesma forma.
Não quero explicar como instala e usa este software, portanto vou me limitar ao script que montei para fazer load balancing. Não foi devidamente testado e em algumas situações foi exibido o erro (1105, '#07000(proxy) all backends are down'), portanto, antes de ir migrando seu servidor, faça muitos testes e verifique mais detalhes.
O script, feito em Lua, ficou assim
function connect_server()
local num = tonumber(os.date("%S")) % 2
proxy.connection.backend_ndx = num 1
print("Using " .. proxy.backends[proxy.connection.backend_ndx].address)
endSalve o arquivo como load_balancing.lua e execute da seguinte forma:
mysql-proxy \
--proxy-lua-script=load_balancing.lua \
--proxy-backend-addresses=127.0.0.1:3306 \
--proxy-backend-addresses=vs2:3306O parâmetro --proxy-backend-addresses pode ser repetido quantas vezes quiser, um para cada réplica do MySQL, quanto mais réplicas, mais poderoso é o seu "cluster".
Os hosts "127.0.0.1" e "vs2" são respectivamente, minha máquina e uma máquina virtual rodando no vmware-server, portanto, use os IPs ou hostnames conforme a sua realidade.
Ele não suporta master/slave (não encontrei nada na documentação que orientasse como fazer nesse caso), portanto foi necessário configurar os hosts como master/master, que você pode ver como fazer em [6]
O funcionamento é assim: nos segundos pares as conexões são repassadas ao servidor 1, e nas ímpares as conexões são repassadas para o servidor 2. Se houvessem 10 servidores, o módulo de 2 (local num = tonumber(os.date("%S")) % 2) seria feito com 10 (local num = tonumber(os.date("%S")) % 10) e o comando de chamada do mysql-proxy teria 10 vezes o parâmetro --proxy-backend-addresses. Simples né?
Cache em memória
Bom, eu já havia feito o mesmo tipo de coisa hackeando a QuerySet e criando o método .cache() [7], que me ajudou muito. Esta solução que eu criei hoje pode ser considerado inferior à anterior por oferecer menos granularidade e funcionar somente com MySQL, mas caso você não queira modificar o código original do Django, esta pode ser uma boa alternativa para você.
Este segundo script funciona da seguinte forma: quando uma consulta do tipo SELECT é feita ao banco de dados, seu resultado é armazenado em um servidor Memcached, com tempo de expiração definido por pattern (cada pattern, ou seja, cada modelo de SELECT pode possuir um tempo de expiração diferente) e se uma cosulta idêntica for requisitada dentro do tempo de expiração, o servidor de cache será consultado, ao invés do banco de dados.
Bacana né?
Então lá vai
-- Packages required
require("Memcached") -- http://luamemcached.luaforge.net/
require("json") -- http://www.chipmunkav.com/downloads/Json.lua
-- Connect to memcached server
local conn = Memcached.Connect('localhost', 11211)
-- Default expire time for cache items
local default_expire_time = 30
-- Prefix for cache keys
local key_prefix = 'mysql-proxy-'
-- Patterns to define expiration time for different types of queries.
-- More details in: http://lua-users.org/wiki/PatternsTutorial
local patterns_expire_time = {
{'^%s*select . from .*auth_user', 150},
{'^%s*select', default_expire_time},
}
-- Converts a string to valid key
function encode_key(str)
return key_prefix .. string.gsub(str, ' ', '-')
end
-- Converts a resultset to JSON. This can't be done by
-- Json library directly because it's a userdata datatype
-- instance
function resultset_to_str(resultset)
local rfields = resultset.fields
local rrows = resultset.rows
local fields = {}
local rows = {}
local pos = 1
-- Rows
for row in rrows do
rows[pos] = row
pos = pos 1
end
-- Fields
pos = 1
for i = 0, #rfields do
if rfields[i] then
fields[pos] = {
type = rfields[i].type,
name = rfields[i].name,
}
end
pos = pos 1
end
return Json.Encode({
fields = fields,
rows = rows,
})
end
-- Callback called before request database server
function read_query(packet)
if string.byte(packet) == proxy.COM_QUERY then
local sql = string.sub(packet, 2)
if string.match(string.lower(sql), '^%s*select') then
-- Transform to valid key string
local key = encode_key(sql)
-- Gets from cache
local rset = conn:get(key)
-- If not found in cache, requests from database server
if rset == nil then
proxy.queries:append(1, packet)
return proxy.PROXY_SEND_QUERY
end
-- Print out
print('from cache', key)
-- Json -> table
rset = Json.Decode(rset)
-- Todo: check for error returns
proxy.response.type = proxy.MYSQLD_PACKET_OK
proxy.response.resultset = rset
return proxy.PROXY_SEND_RESULT
end
end
end
-- Callback called after request to database server
function read_query_result(inj)
local res = resultset_to_str(inj.resultset)
local sql = string.lower(string.sub(inj.query, 2))
local key = encode_key(sql)
local expire_time = default_expire_time
-- Looks at patterns for respective expire time
for i = 0, #patterns_expire_time do
if patterns_expire_time[i] and string.match(sql, patterns_expire_time[i][1]) then
expire_time = patterns_expire_time[i][2]
break
end
end
-- Saves to cache
conn:set(key, res, expire_time)
endLá no início, as linhas que definem local conn e local patterns_expire_time devem ser ajustadas à sua realidade (endereço do servidor e patterns). Para saber como definir expressões regulares em Lua veja em [8] (é um pouquinho diferente do convencional).
Este script depende de dois pacotes externos à linguagem: json.lua [9] e Memcached.lua [10], que por sua vez depende do LuaSocket [11].
No Ubuntu, quando se instala o pacote mysql-proxy a lib da Lua 5.0 é instalada também, mas acontece que os pacotes que eu citei acima - especialmente o LuaSocket - não funcionam corretamente com a versão 5.0.
A solução foi instalar a versão 5.1 em paralelo (o LuaSocket possui um pacote no Ubuntu chamado liblua5.1-socket2) e eliminar a pasta antiga de bibliotecas da versão 5.0 (/usr/share/lua/50/), criando um symlink da versão 5.1 (/usr/share/lua/5.1/) com o mesmo nome.
Ainda foi necessário definir a seguinte variável de ambiente
export LUA_INIT=@/usr/share/lua/50/compat-5.1.luaPor fim, para dar vida ao script, basta executar
mysql-proxy \
--proxy-lua-script=cached_queries.lua \
--proxy-backend-addresses=127.0.0.1:3306
Ao executar este comando, será aberta a porta 4040 que deve ser setada na setting DATABASE_PORT. Caso queira saber como sobrepor a porta 3306, veja em [19].
Uma observação importante: o MySQL possui um bug [12] (ou sei lá o que é) que obriga conexões para "localhost" serem via porta 3306, isso vale tanto para o client quanto para o pacote MySQLdb do Python. Você faz uma conexão para a porta 1365465321 ou qualquer outra e ele aponta para 3306. Portanto, ao usar a porta 4040, mude a setting DATABASE_HOST para "127.0.0.1" caso esteja usando "localhost".
No script acima há ainda uma séria limitação quanto ao tamanho da SELECT. Caso ela tenha mais que 245 caracteres, você terá um erro de tamanho da chave no cache. A solução é converter a expressão SELECT com MD5, SHA ou outor algorítimo que crie uma string única em cima de uma SELECT complexa. Eu não consegui fazer isso ainda, mas recomendo expressamente que faça esse ajuste eu espere que eu o faça antes de colocar em um servidor de produção.
Para mais detalhes sobre MySQL Proxy, veja em [13], [14], [15] e [16]
Para mais detalhes sobre Lua, veja em [17] e [18].
É isso aí... artigo feito no fim da noite tem quer ser rápido assim. Dúvidas, é só falar :)
PS: apesar de serem muito úteis para quem usa Django, todas essas configurações são compatíveis com qualquer linguagem ou sistema operacional, e com versões igual ou acima de 5.1 do MySQL.
PS2: agradeço ao Javier Guerra e David Given pelos esclarecimentos sobre a arquitetura da Lua e ao Giuseppe Maxia pelo bom tutorial [19] que desenvolvou sobre MySQL Proxy
PS3: essa foi a primeira vez na vida que escrevi algo em Lua, por favor, sinta-se à vontade para apontar eventuais falhas
Atualização: removendo a variável de ambiente LUA_INIT, o mysql-proxy rodou normalmente sobre a versão 5.1.
Links relacionados
[1]http://dev.mysql.com/downloads/cluster/index.html
[2]http://forge.mysql.com/wiki/MySQL_Proxy
[3]http://dev.mysql.com/doc/refman/5.1/en/connector-j-reference-replication-connection.html
[4]http://softwaremaniacs.org/soft/mysql_cluster/en/
[5]http://blog.globoi.com/producao/2008/04/16/brasileiros-na-mysql-conference/
[6]http://www.howtoforge.org/mysql_master_master_replication
[7]http://marinho.webdoisonline.com/blog/p/metodo-cache-para-queryset_158/
[8]http://lua-users.org/wiki/PatternsTutorial
[9]http://www.chipmunkav.com/downloads/Json.lua
[10]http://luamemcached.luaforge.net/
[11]http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/
[12]https://bugs.launchpad.net/ubuntu/ source/mysql-dfsg-5.0/ bug/241802
[13]http://del.icio.us/marinho/mysql escalabilidade
[14]http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy.html
[15]http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy-scripting.html
[16]http://classdump.org/articles/2008/02/14/mysql-proxy-enhancements
[17]http://www.lua.org/manual/5.1/pt/
[18]http://lua-users.org/wiki/TutorialDirectory
[19](1, 2) http://dev.mysql.com/tech-resources/articles/proxy-gettingstarted.html [Less]
Posted about 1 hour ago
Ontem ao fim do dia foi anunciada a versão 1.0 alpha [1], que é o primeiro passo para a release 1.0 final, a ser lançada em setembro, na DjangoCon.
A maior parte das features da versão 1.0 tem sido utilizada há meses (algumas há anos)
... [More]
através da versão trunk, pois havia muito tempo que a versão 0.96 foi liberada e a maior parte dos desenvolvedores usam a versão do trunk em produção.
O que podemos destacar nesta versão
Suporte a Unicode - a versão anterior não era 100% unicode, o que gerava algumas dificuldades na implantação e distorções entre máquina de desenvolvimento e servidor em produção. Essa feature mudou definitivamente o nosso trabalho pois a partir do momento em que ela foi liberada no trunk, deixamos de nos preocupar com codificação de caracteres e o trabalho passou a render mais.
Escape como default nas variáveis no template - esta feature fez uma diferença fundamental na segurança das aplicações, uma inversão pequena que melhorou bastante os resultados dos projetos.
ORM refatorado com heranças de modelo - há 2 ou 3 meses essa feature foi liberada, possibilitando trabalhar com heranças de classes de modelo, tanto de forma abstrata quanto de forma distribuída em tabelas, isso possibilita que você tenha classes de modelo que são baseadas em outras e seus dados são exibidos de forma transparente.
Admin baseado em NewForms - a última grande novidade, liberada no último sábado foi o merge do branch newforms-admin, que traz consigo todo o Admin refatorado para ser modular e compatível com NewForms. Assim, todo o acoplamento entre classes de modelo e admin foi removido e foi definido um módulo admin.py para conter as classes e definições da aplicação para o Admin. Com o novo admin é possível trabalhar de forma muito mais profissional com sistemas diversos, customizar sua interface de administração do site, adicionar recursos de Ajax, etc.
As próximas versões a serem lançadas na programação da 1.0 serão: 1.0 beta 1, 1.0 beta 2, 1.0 RC 1, 1.0 RC 2 e finalmente, a 1.0 final, prevista para o dia 2 de setembro, na DjangoCon.
Vantagens da versão 1.0
A versão 1.0 é anciosamente esperada pois com o crescimento exorbitante do framework neste último ano fez com que ele ultrapassasse a fronteira técnica, chegando até os diretores de tecnologia, empresários e grandes empresas. Desta forma, uma versão padrão vai permitir uma melhor profissionalização do uso do framework, tanto no uso do Google App Engin [2] quanto no treinamento de profissionais, edição de livros, redação de cursos, etc.
Links relacionados
[1]http://www.djangoproject.com/documentation/release_notes_1.0_alpha/
[2]http://code.google.com/appengine/ [Less]
Posted 2 days ago
Foi lançando o primeiro release de uma série que será lançada até o lançamento do Django 1.0 previsto para Setembro de 2008.
As principais novidades dessa versão com relação ao Django 0.96 são:
O admin foi refatorado
... [More]
(newforms-admin)
Suporte total a Unicode
Melhorias no ORM
Autoescape automático em variáveis de template
Entre outras melhorias menores e correções de erros.
As incompatibilidades podem serem vistas nesse link: http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges [Less]
Posted 2 days ago
Ontem ao fim do dia foi anunciada a versão 1.0 alpha [1], que é o primeiro passo para a release 1.0 final, a ser lançada em setembro, na DjangoCon.
A maior parte das features da versão 1.0 tem sido utilizada há meses (algumas há anos)
... [More]
através da versão trunk, pois havia muito tempo que a versão 0.96 foi liberada e a maior parte dos desenvolvedores usam a versão do trunk em produção.
O que podemos destacar nesta versão
Suporte a Unicode - a versão anterior não era 100% unicode, o que gerava algumas dificuldades na implantação e distorções entre máquina de desenvolvimento e servidor em produção. Essa feature mudou definitivamente o nosso trabalho pois a partir do momento em que ela foi liberada no trunk, deixamos de nos preocupar com codificação de caracteres e o trabalho passou a render mais.
Escape como default nas variáveis no template - esta feature fez uma diferença fundamental na segurança das aplicações, uma inversão pequena que melhorou bastante os resultados dos projetos.
ORM refatorado com heranças de modelo - há 2 ou 3 meses essa feature foi liberada, possibilitando trabalhar com heranças de classes de modelo, tanto de forma abstrata quanto de forma distribuída em tabelas, isso possibilita que você tenha classes de modelo que são baseadas em outras e seus dados são exibidos de forma transparente.
Admin baseado em NewForms - a última grande novidade, liberada no último sábado foi o merge do branch newforms-admin, que traz consigo todo o Admin refatorado para ser modular e compatível com NewForms. Assim, todo o acoplamento entre classes de modelo e admin foi removido e foi definido um módulo admin.py para conter as classes e definições da aplicação para o Admin. Com o novo admin é possível trabalhar de forma muito mais profissional com sistemas diversos, customizar sua interface de administração do site, adicionar recursos de Ajax, etc.
As próximas versões a serem lançadas na programação da 1.0 serão: 1.0 beta 1, 1.0 beta 2, 1.0 RC 1, 1.0 RC 2 e finalmente, a 1.0 final, prevista para o dia 2 de setembro, na DjangoCon.
Vantagens da versão 1.0
A versão 1.0 é anciosamente esperada pois com o crescimento exorbitante do framework neste último ano fez com que ele ultrapassasse a fronteira técnica, chegando até os diretores de tecnologia, empresários e grandes empresas. Desta forma, uma versão padrão vai permitir uma melhor profissionalização do uso do framework, tanto no uso do Google App Engin [2] quanto no treinamento de profissionais, edição de livros, redação de cursos, etc.
Links relacionados
[1]http://www.djangoproject.com/documentation/release_notes_1.0_alpha/
[2]http://code.google.com/appengine/ [Less]
Posted 5 days ago
Brian Rosner acabou de anunciar na lista oficial do Django que foi feito o merge do newforms-admin branch com a versão em desenvolvimento do Django (trunk).
Quem ainda não utiliza o newforms-admin, cuidado ao usar a versão em desenvolvimento do Django. Para quem já usa, bom proveito!
Posted 6 days ago
Estava eu, rodando pela internet no site da comunidade oficial do django quando me deparei com dois novos screencasts nesse site, e como sou bem curioso fui olhar.
O primeiro nem me chamou muito a atenção, a não ser pelo nível de organização do cara que é bastante interessante, porém no segundo ele apresentou uma aplicação [...]
Posted 6 days ago
O Passenger ou mod_rails, é uma maneira de fazer deploy de aplicações Ruby/Rails de maneira simples e fácil no Apache. Nesse post não vou explicar detalhes sobre ele, mas, há esse post do Akita sobre o Passenger que é bem interessante. O
... [More]
Passenger suporta o padrão WSGI, e com isso é possível fazer o deploy de uma aplicação feita em Django utilizando o Passenger.
Antes de explicar sobre como fazer isso, que é algo extremamente simples e fácil. Tem um fato interessante nisso tudo. Porque uma ferramenta para deploy de aplicações Rails suportaria Django?
A resposta (segundo o Akita) é Leah Culver. Acho que se a moda pegar, veremos vários 'easy deployers' aparecendo.
Antes de voltar ao tema principal deste post, outra feliz coincidência é , no qual ele explica mais detalhadamente sobre o WSGI, que tem tudo haver com o assunto.
E agora vamos lá:
Primeiramente é necessário instalar o Passenger. Isso está bem documentado no site do próprio passenger: http://www.modrails.com/install.html por isso não vou descrever esse passo.
Para fazer o Django funcionar é necessário apenas criar um script que 'execute' o Django com o seu handler wsgi e configurar o diretório onde fica esse script, nos arquivos de configuração do Apache.
Vamos começar criando nosso arquivo que executará o WSGI. Vamos chamar ele de passenger_wsgi.py
#!/usr/bin/env python
import os, sys
sys.path.insert(0, '/home/seunome/projetos/') #diretorio onde fica seus projetos
os.environ['DJANGO_SETTINGS_MODULE'] = 'meuprojeto.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
Feito isso vamos configurar o apache. Setando a variável DocumentRoot com o diretório onde está o seu script wsgi 'public'. Devido a uma convenção (Rails) a pasta tem que ter esse diretório 'public'
Para exemplificar, se o seu projeto tiver o diretorio '/var/www/meuprojeto/' o DocumentRoot tem que ser '/var/www/meuprojeto/public'
<VirtualHost *>
DocumentRoot /var/www/meuprojeto/public
</VirtualHost>
Na minha conclusão, se sua hospedagem suportar o mod_wsgi use ele. Senão suportar mod_wsgi e suportar passenger, use o passenger!
Em breve falarei mais sobre o Django no passenger. [Less]
Posted 9 days ago
Que tal aprender uma nova linguagem enquanto faz uma boa tarefa para melhorar a escalabilidade do banco de dados?
Bom, foi isso que eu fiz hoje durante quase todo o dia. Descobri no início do dia que Lua é a linguagem de script do MySQL
... [More]
Proxy e mergulhei pra fazer duas coisas bastante interessantes: Load Balancing e Cache de resultados.
Load Balancing
Eu conheço 3 formas de fazer load balancing com MySQL:
MySQL Cluster [1]
MySQL Proxy [2]
MySQL Master/Slave [3]
Mas na verdade não conhecia em profundidade nenhum deles, apenas uma idéia superficial.
No final de semana testei o Master/Slave. Parece ser o mais poderoso dentre os três métodos, mas usá-lo no Django significa mexer em boa parte do código do ORM. Não é a minha intenção. Cheguei a construir um backend pra isso, em cima do backend do MySQL, e até descobri que havia outro semelhante [4]. Mas nem o meu backend improvisado, nem o do Ivan Sagalaev me seduziram: muito limitado e cheio de falhas de design.
Pois bem, o MySQL Cluster também me desanimou depois que eu li diversos depoimentos contrários [5].
Sobrou o MySQL Proxy.
MySQL Proxy
Este é um software um tanto recente da MySQL, que basicamente faz a ponte entre o(s) servidor(es) de MySQL e a sua aplicação. É vantajoso pois é independente de linguagem ou framework: você desenha as regras e o que vem depois delas pouco importa, funciona da mesma forma.
Não quero explicar como instala e usa este software, portanto vou me limitar ao script que montei para fazer load balancing. Não foi devidamente testado e em algumas situações foi exibido o erro (1105, '#07000(proxy) all backends are down'), portanto, antes de ir migrando seu servidor, faça muitos testes e verifique mais detalhes.
O script, feito em Lua, ficou assim
function connect_server()
local num = tonumber(os.date("%S")) % 2
proxy.connection.backend_ndx = num 1
print("Using " .. proxy.backends[proxy.connection.backend_ndx].address)
endSalve o arquivo como load_balancing.lua e execute da seguinte forma:
mysql-proxy \
--proxy-lua-script=load_balancing.lua \
--proxy-backend-addresses=127.0.0.1:3306 \
--proxy-backend-addresses=vs2:3306O parâmetro --proxy-backend-addresses pode ser repetido quantas vezes quiser, um para cada réplica do MySQL, quanto mais réplicas, mais poderoso é o seu "cluster".
Os hosts "127.0.0.1" e "vs2" são respectivamente, minha máquina e uma máquina virtual rodando no vmware-server, portanto, use os IPs ou hostnames conforme a sua realidade.
Ele não suporta master/slave (não encontrei nada na documentação que orientasse como fazer nesse caso), portanto foi necessário configurar os hosts como master/master, que você pode ver como fazer em [6]
O funcionamento é assim: nos segundos pares as conexões são repassadas ao servidor 1, e nas ímpares as conexões são repassadas para o servidor 2. Se houvessem 10 servidores, o módulo de 2 (local num = tonumber(os.date("%S")) % 2) seria feito com 10 (local num = tonumber(os.date("%S")) % 10) e o comando de chamada do mysql-proxy teria 10 vezes o parâmetro --proxy-backend-addresses. Simples né?
Cache em memória
Bom, eu já havia feito o mesmo tipo de coisa hackeando a QuerySet e criando o método .cache() [7], que me ajudou muito. Esta solução que eu criei hoje pode ser considerado inferior à anterior por oferecer menos granularidade e funcionar somente com MySQL, mas caso você não queira modificar o código original do Django, esta pode ser uma boa alternativa para você.
Este segundo script funciona da seguinte forma: quando uma consulta do tipo SELECT é feita ao banco de dados, seu resultado é armazenado em um servidor Memcached, com tempo de expiração definido por pattern (cada pattern, ou seja, cada modelo de SELECT pode possuir um tempo de expiração diferente) e se uma cosulta idêntica for requisitada dentro do tempo de expiração, o servidor de cache será consultado, ao invés do banco de dados.
Bacana né?
Então lá vai
-- Packages required
require("Memcached") -- http://luamemcached.luaforge.net/
require("json") -- http://www.chipmunkav.com/downloads/Json.lua
-- Connect to memcached server
local conn = Memcached.Connect('localhost', 11211)
-- Default expire time for cache items
local default_expire_time = 30
-- Prefix for cache keys
local key_prefix = 'mysql-proxy-'
-- Patterns to define expiration time for different types of queries.
-- More details in: http://lua-users.org/wiki/PatternsTutorial
local patterns_expire_time = {
{'^%s*select . from .*auth_user', 150},
{'^%s*select', default_expire_time},
}
-- Converts a string to valid key
function encode_key(str)
return key_prefix .. string.gsub(str, ' ', '-')
end
-- Converts a resultset to JSON. This can't be done by
-- Json library directly because it's a userdata datatype
-- instance
function resultset_to_str(resultset)
local rfields = resultset.fields
local rrows = resultset.rows
local fields = {}
local rows = {}
local pos = 1
-- Rows
for row in rrows do
rows[pos] = row
pos = pos 1
end
-- Fields
pos = 1
for i = 0, #rfields do
if rfields[i] then
fields[pos] = {
type = rfields[i].type,
name = rfields[i].name,
}
end
pos = pos 1
end
return Json.Encode({
fields = fields,
rows = rows,
})
end
-- Callback called before request database server
function read_query(packet)
if string.byte(packet) == proxy.COM_QUERY then
local sql = string.sub(packet, 2)
if string.match(string.lower(sql), '^%s*select') then
-- Transform to valid key string
local key = encode_key(sql)
-- Gets from cache
local rset = conn:get(key)
-- If not found in cache, requests from database server
if rset == nil then
proxy.queries:append(1, packet)
return proxy.PROXY_SEND_QUERY
end
-- Print out
print('from cache', key)
-- Json -> table
rset = Json.Decode(rset)
-- Todo: check for error returns
proxy.response.type = proxy.MYSQLD_PACKET_OK
proxy.response.resultset = rset
return proxy.PROXY_SEND_RESULT
end
end
end
-- Callback called after request to database server
function read_query_result(inj)
local res = resultset_to_str(inj.resultset)
local sql = string.lower(string.sub(inj.query, 2))
local key = encode_key(sql)
local expire_time = default_expire_time
-- Looks at patterns for respective expire time
for i = 0, #patterns_expire_time do
if patterns_expire_time[i] and string.match(sql, patterns_expire_time[i][1]) then
expire_time = patterns_expire_time[i][2]
break
end
end
-- Saves to cache
conn:set(key, res, expire_time)
endLá no início, as linhas que definem local conn e local patterns_expire_time devem ser ajustadas à sua realidade (endereço do servidor e patterns). Para saber como definir expressões regulares em Lua veja em [8] (é um pouquinho diferente do convencional).
Este script depende de dois pacotes externos à linguagem: json.lua [9] e Memcached.lua [10], que por sua vez depende do LuaSocket [11].
No Ubuntu, quando se instala o pacote mysql-proxy a lib da Lua 5.0 é instalada também, mas acontece que os pacotes que eu citei acima - especialmente o LuaSocket - não funcionam corretamente com a versão 5.0.
A solução foi instalar a versão 5.1 em paralelo (o LuaSocket possui um pacote no Ubuntu chamado liblua5.1-socket2) e eliminar a pasta antiga de bibliotecas da versão 5.0 (/usr/share/lua/50/), criando um symlink da versão 5.1 (/usr/share/lua/5.1/) com o mesmo nome.
Ainda foi necessário definir a seguinte variável de ambiente
export LUA_INIT=@/usr/share/lua/50/compat-5.1.luaPor fim, para dar vida ao script, basta executar
mysql-proxy \
--proxy-lua-script=cached_queries.lua \
--proxy-backend-addresses=127.0.0.1:3306
Ao executar este comando, será aberta a porta 4040 que deve ser setada na setting DATABASE_PORT. Caso queira saber como sobrepor a porta 3306, veja em [19].
Uma observação importante: o MySQL possui um bug [12] (ou sei lá o que é) que obriga conexões para "localhost" serem via porta 3306, isso vale tanto para o client quanto para o pacote MySQLdb do Python. Você faz uma conexão para a porta 1365465321 ou qualquer outra e ele aponta para 3306. Portanto, ao usar a porta 4040, mude a setting DATABASE_HOST para "127.0.0.1" caso esteja usando "localhost".
No script acima há ainda uma séria limitação quanto ao tamanho da SELECT. Caso ela tenha mais que 245 caracteres, você terá um erro de tamanho da chave no cache. A solução é converter a expressão SELECT com MD5, SHA ou outor algorítimo que crie uma string única em cima de uma SELECT complexa. Eu não consegui fazer isso ainda, mas recomendo expressamente que faça esse ajuste eu espere que eu o faça antes de colocar em um servidor de produção.
Para mais detalhes sobre MySQL Proxy, veja em [13], [14], [15] e [16]
Para mais detalhes sobre Lua, veja em [17] e [18].
É isso aí... artigo feito no fim da noite tem quer ser rápido assim. Dúvidas, é só falar :)
PS: apesar de serem muito úteis para quem usa Django, todas essas configurações são compatíveis com qualquer linguagem ou sistema operacional, e com versões igual ou acima de 5.1 do MySQL.
PS2: agradeço ao Javier Guerra e David Given pos esclarecimentos sobre a arquitetura da Lua e ao Giuseppe Maxia pelo bom tutorial [19] que desenvolvou sobre MySQL Proxy
PS3: essa foi a primeira vez na vida que escrevi algo em Lua, por favor, sinta-se à vontade para apontar eventuais falhas
Links relacionados
[1]http://dev.mysql.com/downloads/cluster/index.html
[2]http://forge.mysql.com/wiki/MySQL_Proxy
[3]http://dev.mysql.com/doc/refman/5.1/en/connector-j-reference-replication-connection.html
[4]http://softwaremaniacs.org/soft/mysql_cluster/en/
[5]http://blog.globoi.com/producao/2008/04/16/brasileiros-na-mysql-conference/
[6]http://www.howtoforge.org/mysql_master_master_replication
[7]http://marinho.webdoisonline.com/blog/p/metodo-cache-para-queryset_158/
[8]http://lua-users.org/wiki/PatternsTutorial
[9]http://www.chipmunkav.com/downloads/Json.lua
[10]http://luamemcached.luaforge.net/
[11]http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/
[12]https://bugs.launchpad.net/ubuntu/ source/mysql-dfsg-5.0/ bug/241802
[13]http://del.icio.us/marinho/mysql escalabilidade
[14]http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy.html
[15]http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy-scripting.html
[16]http://classdump.org/articles/2008/02/14/mysql-proxy-enhancements
[17]http://www.lua.org/manual/5.1/pt/
[18]http://lua-users.org/wiki/TutorialDirectory
[19](1, 2) http://dev.mysql.com/tech-resources/articles/proxy-gettingstarted.html [Less]
Posted 12 days ago
Já faz alguns meses que eu queria fazer alguns testes com o mod_wsgi, a alternativa ao mod_python que vem sendo cada vez mais comentada e sugerida na comunidade Django.
Ontem eu aproveitei o dia tranquilo pra unir o útil ao agradável e
... [More]
colocar isso em prática.
O que é WSGI?
Vamos "começar do começo": WSGI é mais simples do que parece, trata-se de uma interface definida pela PEP 333 [1] para intermediar a comunicação entre servidores web e frameworks Python. Ela surgiu pela necessidade qualquer um de nós nota com um pouco tempo: Python tem quilos de frameworksmuitas delas excelentes, e às vezes o mod_python parece ser um ornitorrinco em um ninho de coelhos quando implantamos alguns sites. É esquisito.
Mudando para o mod_wsgi
Para usar o WSGI puramente, é bastante simples, vamos fazer um pequeno script para ilustrar
def application(environ, start_response):
status_code = '200 OK'
headers = [('Content-Type', 'text/html')]
start_response(status_code, headers)
return ['So <b>testando</b>!']
from paste import httpserver
httpserver.serve(application, port='8000')Ao executar este script minúsculo e carregar em seu navegador a URL "http://localhost:8000", será exibido "So testando". Simples não? Veja
$ python my_app.py
serving on http://127.0.0.1:8000
No Django, a coisa exige só um pouco mais de detalhes, mas nada complexo. Como já explanado há poucos dias pelo Eric, da Metaphormedia, em [2], é recomendável que se crie dentro de seu projeto uma pasta "apache" com um script dentro chamado "django.wsgi" - estes nomes são escolha sua, mas eles têm sido usados por mais de uma referência. Com o conteúdo abaixo (modificado segundo as suas necessidades)
#!/usr/bin/env python
import os, sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) '/../'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()E configure seu apache, removendo o código que implementa o mod_python e adicionando o seguinte
WSGIScriptAlias / /caminho/do/seu/projeto/apache/django.wsgiAgora, para funcionar, é preciso que seu sistema operacional tenha o mod_wsgi instalado. No Ubuntu/Debian você instala com o seguinte comando
$ sudo apt-get install libapache2-mod-wsgiPronto, ao reiniciar o Apache, seu projeto estará lá, bonitão, rodando através do WSGI :)
Porque mod_wsgi?
Bom, essa deve ser a pergunta que você deve estar se fazendo. Se o mod_python sempre foi a alternativa recomendada e funciona bem, porque mudar para o mod_wsgi?
O mod_wsgi tem se comportado mais escalável que o mod_python. Por escalabilidade, entende-se a capacidade de um site de suportar o crescimento de uso sem perder a performance ou travar.
Ao ajustar um servidor para usar o mod_wsgi, pude notar essas diferenças. Elas não são tão grandes que se possa perceber com um simples teste com Apache Bench. É necessário forçar para notar a diferença, que tem sido até 10% superior.
Esse servidor que usei para fazer esses testes é uma VM (em Xen) com 512MB de RAM, rodando em um amd64, numa rede bastante estável, em Londres, enquanto que o Apache Bench foi executado em minha máquina.
Veja abaixo como os dois modos se comportaram:
Para compreender a legenda, o número após o "n" trata-se da quantidade de requisições enviadas e o número após o "c" trata-se da quantidade de requisições concorrentes, ou seja, "n1000c5" equivale a "1000 requisições enviadas de 5 em 5).
Tempo de resposta por requisição
Requisições por segundo
Load de 1 minuto
Load de 5 minutos
RSS / Memória real ocupada
Percentual da CPU
Conclusões
Todos os números apresentados acima não podem ser tomados como base exata para comparação, por alguns motivos, como a influência que o teste anterior efetua no subsequente, a oscilação da internet e a influência de outros processos.
Eu fiz questão de usar um servidor em produção onde está sendo servido um outro sistema da empresa, que mesmo que não estivesse em seu horário de pico, era usado normalmente por alguns usuários.
Há ainda toda a questão de controvérsias em torno do uso real da CPU e da memória.
Mas dá pra notar uma realidade clara: o mod_swgi se mostra mais vantajoso em memória e tempo de resposta. Não esqueçamos que 200 requisições concorrentes para um VPS com 512MB de RAM é uma senhora carga, equivalente a alguns milhões de pageviews por mês.
No teste de 2000 requisições a 200 concorrentes, ficou visível que a configuração de MaxRequestsPerChild (de 1000) entrou em cena e fez diferença no desempenho, tanto para segurar a carga quando para mudar um pouco a evolução do desempenho.
Por fim, devo avisar que no teste seguinte, de 5000 requisições a 200 concorrentes, o Apache caiu antes de completar 3000, em 3 tentativas.
Mais detalhes sobre o WSGI podem ser encontrados em [4], [5] e [6]
Links relacionados
[1]http://www.python.org/dev/peps/pep-0333/
[2]http://www.ericholscher.com/blog/2008/jul/8/setting-django-and-mod_wsgi/
[3]http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango
[4]http://www.slideshare.net/hdiogenes/wsgi-a-resposta-para-a-questo-definitiva-sobre-python-a-web-e-tudo-mais-368429?src=embed
[5]http://en.wikipedia.org/wiki/Wsgi
[6]http://www.wsgi.org/wsgi/
[7]http://del.icio.us/marinho/wsgi [Less]
Posted 12 days ago
A chamada de trabalhos para a PyConBrasil 2008 se encerra neste domingo, dia 13 de julho. Ainda dá tempo de você enviar a sua proposta. A PyConBrasil 2008 ocorrerá no Rio de Janeiro, de 18 a 20 de setembro.
Os trabalhos se dividem em
... [More]
três categorias:
Palestras: Entre 30 minutos e 1h de duração.
Palestras-relâmpago: Entre 5 e 20 minutos de duração
Treinamentos: Tutoriais e treinamentos com duração entre 2h e 2 dias.
A PyConBrasil 2008 será realizada na Universidade Veiga de Almeida, no Rio de Janeiro, entre os dias 18 e 20 de setembro.
Para enviar sua proposta de trabalho, acesse http://pyconbrasil.com.br/sobre-o-evento/chamada-de-trabalhos. [Less]
Posted 13 days ago
O Leandro Severino anunciou hoje à tarde a seguinte oportunidade de trabalho:
Prezados,
A LS Tecnologia buscando reforçar seu quadro de talentos esta
selecionando Programadores / Desenvolvedores com o seguinte
... [More]
perfil:
Fundamentais:
Conhecimento em Python, experiência minima de 1,5 / 02 anos.
Conhecimento em Programação Orientada à Objetos.
Conhecimento em algum Banco de Dados Relacional.
Desejáveis:
Conhecimentos em Python para Desktop (PyGTK, wxPython, Glade
System Message: WARNING/2 (<string>, line 17)
Bullet list ends without a blank line; unexpected unindent.
e etc).
- Conhecimento em Python para Web(Django, Pylons, PSP).
- Conhecimento em Softwares de controle de versão como o CVS,
Subversion.
Interessados devem encaminhar o currículo com pretenção salarial
para CLT e PJ para rh arroba lstecnologia ponto com ponto br colocando
no assunto o titulo da vaga.
Local de trabalho: Porto Alegre, Canoas - RS.
A empresa fornece benefícios e remuneração compatíveis com o
mercado de trabalho atual. [Less]
Posted 13 days ago
A mensagem foi enviada por Michel Thadeu Sabchuk, como abaixo:
Olá pessoal,
Trabalho numa empresa gestora de portais na internet, estamos
selecionando programador de python com domínio do framework django,
conhecimento de
... [More]
montagem de páginas usando css e programação client
side através de javascript.
O ambiente da empresa é muito bom e os projetos são desafiadores ;)
Interessados por favor enviem currículos para michel em misterape com,
qualquer dúvida me contactem no mesmo email.
Atenciosamente, [Less]
Posted 13 days ago
Enfim, estou aqui para escrever algo sobre o branch do NewForms-Admin [1].
Nas últimas semanas tenho convivido diariamente com Bazaar [2], NewForms-Admin e problemas dos mais variados com meu PC principal. Destes, apenas o terceiro não é
... [More]
uma ótima coisa, mas me excitei bastante com o assunto de hoje.
Quem tem alguma convivência comigo no assunto de Django, sabe muito bem da minha birra com o Admin e helpers do NewForms (form_for_instance e form_for_model). É que esses dois em especial sempre se apresentaram boas ferramentas para Hello World* mas nada eficazes para a prática. O Admin antigo ainda tinha um problema de filosofia, misturando as definições, que eram distribuídas pelas classes de modelo afora.
Mas as maravilhas que o NewForms tem feito estão fazendo ótimos efeitos. O ModelForms foi realmente uma boa sacada, e o NewForms-Admin também, que comecei a utilizar por recomendação do Yuri [3], que já vem utilizando-o desde junho e que iríamos usar uma aplicação em comum, e acabei por gostando muito desta nova versão do contrib.
Pois bem, vamos ao que interessa.
O que é o NewForms-Admin
Quem conviveu com os forms antigos (oldforms [4]) sabe a grande diferença que fez os novos forms (newforms [5]), mas uma das coisas que impedem de os forms antigos serem eliminados por completo é exatamente o Admin, uma convidativa e útil ferramenta do framework.
Este branch - um branch é uma ramificação do projeto com um objetivo que, ao ficar completa, volta a fazer parte do tronco oficial, o trunk - tem o objetivo de sanar alguns erros de filosofia e aplicar os NewForms ao Admin, ou seja, resolver todos seus problemas críticos.
É importante informar que o branch é um Django completo, que vez por outra é sincronizado com o trunk, garantindo assim suas melhorias constantes, ainda que com um pequeno delay. Portanto, o branch trata-se de um Django completo, porém, diferente.
Baixando o branch
Antes de baixar e instalar o branch é importante salientar que ainda que ele esteja muito estável e consistente, ainda assim, é um branch. Portanto, é recomendável que mantenha o trunk instalado normalmente como instalação oficial do Django, indicando manualmente em seu projeto a versão do branch - você verá como fazer isso neste artigo.
Faça o download o branch, utilizando-se do svn [6]:
svn co http://code.djangoproject.com/svn/django/branches/newforms-admin/
Faça isso de preferência em alguma pasta semelhante à esta (o meu caso) - observando que este caminho é típico de Linux/Unix/MacOSX, mas você pode se utilizar de um caminho parecido no Windows:
/home/django/
Será criada uma pasta no seguinte caminho, contendo o Django completo - incluindo documentação, testes, etc.:
/home/django/newforms-admin/
Preparando um projeto de primeiras impressões
Agora crie seu projeto, vamos dar a ele o nome de "meu_projeto":
django-admin.py startproject meu_projeto
No meu caso, foi criado um projeto no seguinte caminho:
/home/marinho/Lab/meu_projeto
Dentro, crio uma aplicação, chamada "financas":
django-admin.py startapp financas
Ok, agora basta proceder com o básico da preparação de um projeto em Django - configurar conexão com o banco de dados, caminho para o templates e configurar INSTALLED_APPS, não se esquecendo de que uma das aplicações que devem ser acrescentadas a esta setting é o 'django.contrib.admin' - este artigo foi criado seguindo a premissa de que o leitor conhece o básico do framework.
Vamos agora modificar o projeto para que ele utilize o Django do branch, e não o instalado nas bibliotecas do Python. Para isso, adicione as seguintes linhas no início do arquivo manage.py:
import os, sys
sys.path = ['/home/django/newforms-admin/'] sys.pathÉ importante que o caminho do branch fique ANTES do sys.path para que ele sobreponha a versão instalada no Python.
Criando as classes de modelo - primeiras diferenças
Vamos criar três classes de modelo para colocar em prática as novidades do branch, como abaixo:
class Historico(models.Model):
descricao = models.CharField(max_length=50)
def __unicode__(self):
return self.descricao
class Pessoa(models.Model):
nome = models.CharField(max_length=50)
def __unicode__(self):
return self.nome
class Lancamento(models.Model):
tipo = models.CharField(max_length=1, choices=(('C', 'Crédito'), ('D', 'Débito')), default='D')
historico = models.ForeignKey('Historico')
pessoa = models.ForeignKey('Pessoa')
valor = models.DecimalField(max_digits=15, decimal_places=2)
data_vencimento = models.DateField(null=True, blank=True)
data_pagamento = models.DateField(null=True, blank=True)
status = models.CharField(max_length=1, choices=(('P', 'Pendente'), ('G', 'Pago')), default='P')
observacoes = models.TextField(null=True, blank=True)
def __unicode__(self):
return "%s - %f" %( self.pessoa, self.valor )Note a ausência da class Admin nas classes - lembrando que o método __unicode__ continua tão importante como sempre foi, assim como a class Meta.
Criando um módulo específico para o Admin
Agora vamos criar um módulo para contêr as parametrizações do Admin, chamado admin.py, dentro da pasta da aplicação:
from django.contrib import admin
from financas.models import Historico, Pessoa, Lancamento
admin.site.register(Historico)
admin.site.register(Pessoa)
admin.site.register(Lancamento)Configurando a URL
Outra mudança essencial foi na forma como se declara a URL do Admin. Veja abaixo como fica a pattern:
(r'^admin/(.*)', admin.site.root),Mas isso ainda não é suficiente porque, como pode observar, o admin.site.root está em Python, o que implica na seguinte importação no início do arquivo urls.py:
from django.contrib import adminOk, isso será suficiente para o projeto funcionar e permitir o acesso à página de administração, entretanto, ao fazer esse teste você irá sentir a falta das classes que parametrizamos no módulo admin.py. Isso ocorre porque esse módulo precisa também ser importado no módulo urls.py, assim:
from financas.admin import *Pronto, ao rodar o runserver irá notar as classes prontamente de volta à produção! Mas ainda não é suficiente, vamos dar uma olhadela em como fazer aqueles mesmos parâmetros que estávamos acostumados na class Admin, se lembra? Para isso, voltemos ao módulo admin.py, que deve agora ficar da seguinte forma:
from django.contrib import admin
from django.contrib.admin.options import ModelAdmin
from financas.models import Historico, Pessoa, Lancamento
class AdminLancamento(ModelAdmin):
list_display = ('pessoa','historico','valor','data_vencimento','status')
search_fields = ('pessoa','historico','valor')
list_filter = ('pessoa','historico','valor','data_vencimento','status')
admin.site.register(Historico)
admin.site.register(Pessoa)
admin.site.register(Lancamento, AdminLancamento)Pronto, agora voltamos ao estágio que estávamos no antigo Admin: parametrizações funcionando corretamente :)
Numa próxima oportunidade, vamos explorar outras novidades do branch, como a classe InlineModelAdmin, os templates (que estão um tanto mais coesos), como utilizar Ajax a torte e a direito, master/detalhe, etc.
Espero que tenha sido util! Um abraço e boa semana para todos!
Links relacionados
[1]http://code.djangoproject.com/wiki/NewformsAdminBranch
[2]http://bazaar-vcs.org/
[3]http://www.buriy.com/
[4]http://www.djangoproject.com/documentation/forms/
[5]http://www.djangoproject.com/documentation/newforms/
[6]http://en.wikipedia.org/wiki/Subversion_(software) [Less]
Posted 13 days ago
Quando eu comecei a usar o Django, eu criava uma página para "exibir mensagens do sistema ao usuário". É uma mania remanescente dos tempos de PHP que resolve bem a necessidade de mostrar coisas como "Senha modificada com
... [More]
sucesso!". Isso se prolongou por mêses, por uma maldita falta de atenção ao ler a documentação [1] do sistema de autenticação.
Pois é, o Django oferece o recurso de mensagens do sistema para fazer justamente isso: mostrar mensagens de aviso ao usuário.
Este sistema funciona como um pool: você lança quantas mensagens quiser em uma instância específica para isso e na primeira oportunidade, aquelas mensagens são despejadas para o usuário, limpa o pool e começa tudo novamente.
A forma mais comum de se mostrar mensagens é criando um espaço em seu template mais básico (aquele do qual todos são herdados) para mostrar essas mensagens. Vamos fazer e ver como funciona.
Registrando mensagens
Primeiramente, é preciso estar ciente de que as mensagens são registradas por usuário, ou seja, este recurso não está disponível para usuários anônimos (usuários que não logaram no sistema). Isso tem solução, que vou mostrar adiante, mas vamos nos atêr ao registro da mensagem.
A qualquer momento, tendo-se um usuário em mãos - normalmente a partir da variável request.user - basta escrever o seguinte código para registrar uma mensagem para aquele usuário:
usuario.message_set.create(message='Mensagem')Na prática, nós poderíamos fazer um código como o seguinte:
@login_required
def excluir(request, cliente_id):
try:
Cliente.objects.get(id=cliente_id).delete()
request.user.message_set.create(message=_('Cliente excluido com sucesso'))
except Cliente.DoesNotExist, e
request.use