terça-feira, 28 de agosto de 2012

Django: miudagens úteis

Sabe aquelas miudagens que temos que fazer com frequência, mas não tanta frequência assim? Claro que tempo o suficiente para não lembrarmos direito como fazer, ter que pesquisar quase tudo de novo, perder aquele baita tempo pra chegar num "Nossa! Não lembrava que era tão simples."?

Bem, é esse tipo de miudagem que vou listar neste post. Para montá-lo fui adicionando a ele todas as miudagens de que precisei várias nos últimos tempos e acabei tendo de pesquisar de novo para relembrar como fazer. Tem mais algumas que adicionei por serem coisas miúdas, mas difíceis de encontrar a solução por aí.



Exibir o conteúdo de uma variável como html puro

Esta dica é bem simples. Sabe quando você tem um conteúdo dentro uma variável que você gostaria que fosse interpretado como html na página e quando você faz um {{var_exemplo}} é dado um escape em todo o conteúdo e caracteres como "<" se tornam "&lt;"?

Esse é o comportamento padrão do django. Por padrão ele vai exibir o conteúdo das variáveis como texto ou como números e dá escape nos caracteres especiais como os sinais de maior e menor para garantir que serão exibidos corretamente em sua página. Além das questões de segurança envolvidas no caso.
Como resolver esse problema? Muito simples! Use a template tag safe. Ao invés de fazer somente {{var_exemplo}} faça {{var_exemplo|safe}} e todo o conteúdo dela será tratado como html.

Fonte: http://stackoverflow.com/questions/4848611/django-rendering-a-template-variable-as-html


Ocultar um campo no admin (tanto ficar oculto quanto não ser usado):

Já precisou criar algum campo que só é usado pelo seu sistema interno e que não deveria por nada aparecer no admin do django? Eu já para armazenar permalinks. E como o campo ficou lá visível é claro que o usuário alterou o conteúdo dele e reclamou dos resultados horrendos. Claro! Ele jurava que não tinha feito nada.

Para evitar esse tipo de situação, uma coisa bem simples de se fazer, que é aceita por todos os campos dos models do django, é adicionar o seguinte parâmetro nos campos a ocultar:
campo_exemplo = models.IntegerField(editable=False)

Prontinho! O seu campo agora é um campo "não editável" e não será exibido no painel de administração.

Agora, se você quer um campo oculto para armazenar a data de criação do objeto:
campo_exemplo = models.DateTimeField(auto_now=True)

Ou, se for um campo para salvar a data da última alteração do objeto:
campo_exemplo = models.DateTimeField(auto_now_add=True)

Fonte: http://stackoverflow.com/questions/6752182/how-to-hide-model-field-in-django-admin


Object managers

Quando vamos fazer consultas sobre objetos do django temos que usar métodos como allfilter, get, etc. Algumas vezes gostaríamos de ter nossos próprios para facilitar nossa vida. Por exemplo, o comportamento do método get: lança exceção se não encontrar nenhum objeto ou se encontrar mais que um. Poderíamos querer, por exemplo, um método get_or_none. Um método que retorne None quando não encontrar nada, ao invés de uma exceção.

Esse método do exemplo é possível, e qualquer um outro que você quiser. Basta aprender a utilizar os object managers. Eles são utilizados por todos os models do django e podem ser sobrescritos.
Veja um exemplo:

class ManagerExemplo(models.Manager):
    def get_or_none(self):
        try:
            aux = super(OrganizationManager,self).get_query_set()
            len(aux) #faz o django executar a consulta de fato
     return aux
        except:
            return None

class Exemplo(models.Model):
    campoqualquer = models.CharField(max_length=100)
    
    objects = ManagerExemplo()
Note que há um atributo objects no model ao qual o manager criado foi atribuído. Com isso novos métodos serão adicionados ao model. No caso do exemplo teríamos: Exemplo.objects.get_or_none()

Fontes: http://stackoverflow.com/questions/4694767/overriding-djangos-relatedmanager-methods e
https://docs.djangoproject.com/en/1.2/topics/db/managers/


Integração com comentários do facebook

Este é bem simples, mas já usei algumas vezes já e acho que vale a pena colocar aqui. Você terá que criar uma aplicação no facebook nesta página aqui. É só entrar no link e clicar no botão a direita para criar a aplicação. Depois você entra nesta página aqui e vai obter um código similar com o código abaixo:



<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=ID_DO_SEU_APP";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
A única coisa que falta nele é o ID_DO_SEU_APP. Ou melhor, substitua esse texto pelo id da sua aplicação no facebook. Se você pegar o código pelo link acima, ele já estará ajustado para a sua aplicação. Essa parte do código você coloca no cabeçalho da sua página.

Esta parte do código deve ser colocada no local onde você quiser que a caixa de comentários apareça:
<div class="fb-comments" data-href="http://example.com" data-num-posts="2" data-width="470"></div>
Não se esqueça de alterar o valor de data-href para as urls das páginas do seu site em que você vai colocar ao invés de http://example.com.

Fontes: http://stackoverflow.com/questions/10638503/django-facebook-comments-integration


Usar ManyToManyField como Inline

Estou desenvolvendo um sistema em que precisei criar relações de muitos para muitos entre dois modelos. Para ajudar um bocado, essa relação teve que ser criada manualmente, pois há atributos associados a ela. E agora? Sem os atributos associados o objeto é exibido normalmente no admin como um select múltiplo. Mas isso não funciona mais. O que fazer?
É até que simples quando vemos a solução.
No models.py:
class Planta(models.Model):
    accepts = models.ManyToManyField('self', symmetrical=False, null=True, blank=True, through="planta.Aceita", related_name="aceita")

class Aceita(models.Model):
    a_planta = models.ForeignKey(Planta, related_name="a_planta")
    favorecida_por = models.ForeignKey(Planta, related_name="favorecida_por")
    intensidade = models.IntegerField(null=True, blank=True, choices=ACEITA_OPCOES_INTENSIDADE, default=0)
    descricao = models.CharField(max_length=255, null=True, blank=True)

Aqui já temos como definir uma relação assimétrica entre objetos do mesmo tipo por meio de uma relação de muitos para muitos definida manualmente em um segundo model com atributos extras.
Até aqui tranquilo. Praticamente tudo era, de certa forma, fácil de encontrar na net. E como fazer para o ManyToManyField ser usado como uma ForeignKey?
Essa parte é resolvida no admin.py:
class AceitaInline(admin.TabularInline):
    model = Planta.accepts.through
    fk_name = 'a_planta'

class PlantaAdmin(admin.ModelAdmin):
    inlines = [AceitaInline,]

Pronto! Já temos tudo funcionando. Note que o fk_name indica qual ForeignKey vai ser usada enquanto que o model está indicando para usar o model intermediário especificado no parâmetro through do campo ManyToManyField.

Fontes: http://stackoverflow.com/questions/3153026/django-1-2-1-inline-admin-for-many-to-many-fields, http://stackoverflow.com/questions/2408989/more-than-1-foreign-keyhttps://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationshipshttps://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ManyToManyField e https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_many/.

Como deixar todos os campos de múltipla escolha com o estilo do admin

O estilo dos selects para mais de uma opção do admin do django é bem legal. Mas quando criamos os nossos modelos de administração o django não o usa. Usa o select box normalzinho. Existem várias formas de resolver isso. Você pode colocar um script no template da página e fazê-lo estilizar os selects que você quiser, pode utilizar um form e fazer a estilização como widget. Ou, a solução que prefiro e vou mostrar aqui por ser bem mais simples, prática e menos intrusiva. Basta adicionar o atributo formfield_overrides com o seguinte valor ao seu model de admin, que TODOS os selects de múltipla escolha dele serão estilizados automaticamente.

class PlantaAdmin(admin.ModelAdmin):
    inlines = [AceitaInline, ]

    formfield_overrides = {models.ManyToManyField: {'widget': FilteredSelectMultiple("verbose name", is_stacked=False)},}

Pronto! Tudo em ordem. ;)
Fontes: http://stackoverflow.com/questions/1698435/django-multi-select-widget


Domínio estranho

Sabe quando você usa o password_reset do Django e o endereço base das urls no email enviado vai para todos com o domínio igual a "www.example.com" e você não acha em lugar nenhum como resolver isso? Então, é bem simples. Esse endereço é obtido a partir do módulo sites do django(django.contrib.sites). Para alterá-lo, basta fazer isto:
from django.contrib.sites.models import Site

obj=Site.objects.get(id=1) #ou Site.objects.get_current() caso esteja usando vários sites
obj.name='Meu site'
obj.domain='www.meusite.com.br'
obj.save()

Ou então, vá no admin do seu projeto, na guia Sites e mude o endereço do que estiver lá para o que você quiser. ;)


Paginação

Paginação é algo simples de fazer, mas sempre dá problemas quando vamos fazer "na mão". Além de demorar mais. Para resolver isso, há um módulo do django chamado Paginator. Ele cuida dessas questões e é fácil de usá-lo:
from django.core.paginator import Paginator
objects = ['john', 'paul', 'george', 'ringo']
p = Paginator(objects, 2) #recebe a lista, queryset, dicionario, etc e o numero de objetos por página

p.num_pages #retorna o numero de paginas: 2
p.page_range #retorna uma lista com indice de todas as paginas: [1, 2]

page1 = p.page(1) #retorna um objeto Page para a primeira pagina
page1.object_list #retorna a lista de objetos desta pagina: ['john', 'paul']

page2.has_next() #verifica se ha uma proxima pagina ou nao
page2.has_previous() #verifica se ha uma pagina anterior ou nao
page2.has_other_pages() #verifica se ha uma pagina anterior ou proxima
page2.next_page_number() #retorna o indice da proxima pagina
page2.previous_page_number() #retorna o indice da pagina anterior

page2.start_index() #o indice iniciado em 1 do primeiro item desta pagina
page2.end_index() #o indice iniciado em 1 do ultimo item desta pagina

p.page(0) #erro: o indice deve ser maior que 0
p.page(3) #erro: a pagina de indice 3 esta vazia

Bem, sabendo desses métodos, acho que já é mais que o suficiente para usá-lo, não é mesmo?
Se não, me avise. Que edito e acrescento um exemplo mais completo. ;)

Fonte: https://docs.djangoproject.com/en/dev/topics/pagination/


Até a próxima!




Nenhum comentário: