sexta-feira, 23 de agosto de 2013

Ordenando por um campo calculado por uma def no admin

Seguindo o exemplo anterior, Como utilizar os inlines no front para os ModelForms (class based view) , imagine que gostaríamos de exibir quantas fotos cada cadastro postou. Fácil né?

Basta fazer uma def no models.py como abaixo e depois colocá-la no list_display do admin.py:

def qtde_fotos(self):
    return self.foto_set.all().count()


Mas e se, quiséssemos ordenar no admin por este valor? Saber em ordem quais os cadastros que mais enviaram fotos? Por ser uma def, o django não ordena automaticamente. Pra isso temos que fazer alguns ajustes. Primeiro de tudo, apague esta def do seu model caso tenha feito.

No admin de seu model no arquivo admin.py, deixe como abaixo:


def queryset(self, request):
    qs = super(CadastroAdmin, self).queryset(request)
    qs = qs.annotate(qtde=Count('foto'))
    return qs

def qtde_fotos(self, inst):
    return inst.qtde
qtde_fotos.admin_order_field = 'qtde'


depois basta adicionar a def qtde_fotos no list_display.


hasta!

Como utilizar os inlines no front para os ModelForms (class based view)

Assim como prometido, mesmo que com uma "pequena" demora, segue a continuação do post
Como utilizar os inlines no front para os ModelForms (function based view). Faremos algo bem similar com alguns detalhes a mais.

Objetivo é fornecer um formulário de cadastro e este pode fazer uploads de fotos. Quando o email já existir no banco de dados, faremos que as fotos do segundo envio seja direcionadas para o primeiro cadastro, do contrário, uma nova instancia para todos os objetos envolvidos.

models.py

from django.db import models

# Create your models here.

class Cadastro(models.Model):
 """(Cadastro description)"""
 nome = models.CharField(max_length=255)
 email = models.EmailField(verbose_name='E-mail')
 telefone = models.CharField(max_length=20)
 aceite = models.BooleanField(default=True)
 data = models.DateField(auto_now_add=True)
 hora = models.TimeField(auto_now_add=True)

 class Meta:
  verbose_name, verbose_name_plural = u"Cadastro" , u"Cadastros"
  ordering = ('-data',)

 def __unicode__(self):
  return "%s" % self.nome

class Foto(models.Model):
 """(Foto description)"""
 cadastro = models.ForeignKey(Cadastro)
 foto = models.ImageField(upload_to="uploads/fotos/foto/%Y")
 data = models.DateField(auto_now_add=True)
 hora = models.TimeField(auto_now_add=True)
 aprovado = models.BooleanField(default=False)

 class Meta:
  verbose_name, verbose_name_plural = u"Foto" , u"Fotos"
  ordering = ('-data',)

 def __unicode__(self):
  return "%s" % self.id

 def thumb_admin(self):
  from sorl.thumbnail import get_thumbnail
  im = get_thumbnail(self.foto, '120x120', crop='center', quality=99)
  if im:
   return  '<a href="http://www.blogger.com/media/%s" onclick="window.open(this.href);return false;"><img alt="Imagem" src="%s" /></a>' % (self.foto, str(im.url))
  return  'Sem foto'
 thumb_admin.is_safe = True
 thumb_admin.allow_tags = True
 thumb_admin.short_description = 'Thumb'

admin.py

from django import forms
from django.contrib import admin
from .models import *

class FotoInline(admin.TabularInline):
 model = Foto
 readonly_fields = ['foto',]
 extra = 5


class CadastroAdmin(admin.ModelAdmin):
 inlines = [
  FotoInline,
 ]
 search_fields = ('nome', 'email',)
 list_display = ('nome', 'email','telefone','data', 'hora',)
 list_filter = ['data',]
 date_hierarchy = 'data'
 readonly_fields = ['nome', 'email','telefone','aceite']
 save_on_top = True

admin.site.register(Cadastro, CadastroAdmin)



class FotoAdmin(admin.ModelAdmin):
 
 list_display = ('cadastro', 'data', 'hora','thumb_admin','aprovado')
 list_filter = ['data','aprovado']
 date_hierarchy = 'data'
 list_editable = ['aprovado']
 readonly_fields = ['cadastro','foto',]
 save_on_top = True

admin.site.register(Foto, FotoAdmin)

forms.py

# coding: utf-8
from django import forms
from django.utils.translation import ugettext_lazy as _
from .models import *
from util import util as U

from django.forms.models import inlineformset_factory

class CadastroForm(forms.ModelForm):
 class Meta:
  model = Cadastro


class FotoForm(forms.ModelForm):

 aprovado = forms.BooleanField(initial=False, required=False)
 aceite = forms.BooleanField(initial=True)

 class Meta:
  model = Foto


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

 def clean(self):
  cleaned_data = super(FotoForm, self).clean()
  #if cleaned_data.get("senha") != cleaned_data.get("confirme"):
  # self._errors["confirme"] = self.error_class(['Senha nao confere'])
  return self.cleaned_data


FotoFormSet = inlineformset_factory(Cadastro, Foto, extra=5)

urls.py

# coding: utf-8
from django.conf.urls.defaults import patterns, include, url

from . import views

urlpatterns = patterns('fotos.views',
 url(r'^$', views.CadastroView.as_view(), name='cadastro_form'),
)

form.html

<html>
 <head>
 <meta charset="utf-8">
 <title></title>
 </head>
 <body>
  <form action="" method="post" enctype="multipart/form-data">
   {% csrf_token %}
   {{ form.errors }}

   <p>Nome:{{ form.nome }}</p>
   <p>Telefone: {{ form.telefone }}</p>
   <p>E-mail: {{ form.email }}</p>
   <p>Foto: {{ form.foto }}</p>
   <p>Aceite: {{ form.aceite }}</p>

   {{ foto_formset.management_form }}
   {% for form in foto_formset %}
    {{ form.id }}
    <div class="form-row inline {{ foto_formset.prefix }}">
     {{ form.foto.label_tag }}
     {{ form.foto }}
     {{ form.foto.errors }}
    </div>
   {% endfor %}

   <input type="submit" name="" value="Enviar">

  </form>
 </body>
</html>

views.py

class CadastroView(CreateView):
 #template_name = 'form.html'
 template_name = 'index.html'
 model = Cadastro
 form_class = CadastroForm
 success_url = '/envie/?sucesso=1#sucesso'

 def form_valid(self, form):
  context = self.get_context_data()
  foto_form = context['foto_formset']

  email = form.cleaned_data['email']

  if foto_form.is_valid():
   print "erros: %s" % foto_form.errors
   if Cadastro.objects.filter(email=email):
    foto_form.instance = Cadastro.objects.get(email=email)
   else:
    self.object = form.save()
    foto_form.instance = self.object
   foto_form.save()
   return HttpResponseRedirect('/envie/?sucesso=1#sucesso')
  else:
   addDebug("erros: %s" % foto_form.errors)
   return self.render_to_response(self.get_context_data(form=form))

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

 def get_context_data(self, **kwargs):
  context = super(CadastroView, self).get_context_data(**kwargs)
  r = self.request.POST
  f = self.request.FILES  

  if r and f:
   context['foto_formset'] = FotoFormSet(self.request.POST,f)
  else:
   context['foto_formset'] = FotoFormSet()
  return context

cadastro.js

jQuery(document).ready(function($) {

 $('.bt-mais').click(function(event) {
  if($('.enviar-foto-fake').length < 5){
   $('.enviar-foto-fake').last().clone().insertAfter($('.enviar-foto-fake').last());
   $('.enviar-foto-fake').last().find('div p.nome-foto').text('Você pode enviar até cinco fotos');
   $('.enviar-foto-fake').last().find('input').attr({
    id: 'id_foto_set-'+($('.enviar-foto-fake').length-1)+'-foto',
    name: 'foto_set-'+($('.enviar-foto-fake').length-1)+'-foto'
   });
   $('#id_foto_set-TOTAL_FORMS').val($('.enviar-foto-fake').length);
  }

  if ($('.enviar-foto-fake').length == 5) {
   $(this).hide();
  };

 })
});

hasta!

Gerando imagens com marca d'agua com django-watermark 0.1.6-pre1

Algumas vezes é necessário que seja feita um tratamento em imagens, para que estas apresentem o logo ou alguma outra informação. Dependendo da utilização, pode simplesmente colocar as imagens como backgrounds de divs com position absolute dentro de um container. Esta abordagem tem dois pontos negativos:

  1. Alguém com o mínimo de conhecimento conseguirá isolaras imagens através do código fonte e obter a imagem original sem a marca facilmente.
  2. Caso seja necessário integração com algum plugin de compartilhamento de conteúdo em redes sociais, por exemplo, a imagem que o plugin pega também é a original.


Então qual a correta forma de solucionar o problema? django-watermark.


Instalação

Para instalação basta utilizar o pip ou easy_install:

easy_install django-watermark
pip install -U django-watermark

Configuração

Para configurá-lo em seu projeto, adicone o aplicativo no INSTALLED_APPS no settings.py

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    ...
    'watermarker',
    ...
)

Execute o syncdb para criar a tabela deste app.

python manage.py syncdb

Opções:


position - Este é bastante personalizável. Primeiro, você pode exibirsua marca d'água em um canto suas imagens usando um dos BR, BL, TR, e TL. Estes representam 'bottom-right', 'bottom-left', 'top-right' e 'top-left', respectivamente.

Alternativamente, você pode usar o posicionamento relativo ou absoluto para a marca d'água. Posicionamento relativo usa porcentagens; posicionamento absoluto utiliza pixels exatos. Você pode misturar e combinar estes dois modos de posicionamento, mas você não pode misturar e combinar relativa / absoluta com o posicionamento canto. Quando se utiliza o posicionamento relativo / absoluto, o valor para o parâmetro de posição é XxY, onde X é o valor da esquerda e o símbolo Y representa o valor superior. Os valores de esquerda e superior devem ser separados com um x minúsculo.

Se você quiser sua imagem marca d'água para mostrar-se no centro de qualquer imagem, você pode utilizar como posição = 50% x50% ou mesmo position = C. Se você queria que a marca d'água para mostrar-se a meio caminho entre as margens esquerda e direita da imagem e 100 pixels da parte superior, você utilizaria = 50% x100.

Finalmente, você pode dizer ao filtro para gerar uma posição para sua marca de água de forma dinâmica. Para fazer isso, use position = R.

opacity - Este parâmetro permite que você especifique a transparência da marca d'água aplicada. O valor deve ser um número inteiro entre 0 e 100, onde 0 é totalmente transparente e 100 é totalmente opaco. Por padrão, a opacidade é fixado em 50%.

tile - Se você quer sua marca d'água para azulejo por toda a imagem, basta especificar um parâmetro como tile= 1. Por padrão, o tile é fixado em 0.

scale - Se você gostaria de ter a marca d'água tão grande quanto possível a imagem de destino e totalmente visível, você pode querer usar scale = F. Se você quiser especificar um fator de escala particular, é só usar algo como scale = 1.43.

greyscale - Se você quer sua marca d'água a ser em tons de cinza, você pode especificar o parâmetro greyscale = 1 e todas saturações de cores vão embora.

rotation - Definir este parâmetro para qualquer inteiro entre 0 e 359 (na verdade, qualquer número inteiro deve funcionar, mas para sua própria sanidade, eu recomendo manter o valor entre 0 e 359). Se você quiser que a rotação ser aleatória, use rotation= R em vez de um número inteiro.

obscure - Defina esse parâmetro como 0 para fazer o nome do arquivo da imagem original do visível para o usuário. O padrão é 1 (ou verdadeiro) para ocultar o nome do arquivo original.

quality - Manter esta a um número inteiro entre 0 e 100 para especificar a qualidade da imagem resultante. O padrão é 85.

random_position_once - Defina como 0 ou 1 para especificar o comportamento de posicionamento aleatório para marca d'água da imagem. Quando ajustado para 0, a marca d'água será colocado aleatoriamente em cada solicitação. Quando ajustado para 1, a marca d'água será posicionado aleatoriamente na primeira solicitação, e os pedidos subsequentes usarão a imagem produzida. O padrão é True (posicionamento aleatório só acontece no primeiro pedido).

Utilização

Sua utilização também é bastante simples. No template que precisar gerar a marca d'agua carrege o filter da aplicação com o comando:

{% load watermarker %}



Exemplos


{{ image_url|watermark:"My Watermark,position=br,opacity=35" }}

Procura uma marca d'água com o nome "My Watermark", coloque-o no canto inferior direito da imagem de destino, utilizando um nível de transparência de 35%.

{{ image_url|watermark:"Your Watermark,position=tl,opacity=75" }}

Procura uma marca d'água com o nome "Your Watermark", coloque-o no canto superior esquerdo da imagem de destino, utilizando um nível de transparência de 75%.

{{ image_url|watermark:"The Watermark,position=43%x80%,opacity=40" }}

Procura uma marca d'água com o nome  "The Watermark", coloca-lo em 43% em relação ao eixo x e de 80% no eixo dos y da imagem do alvo, a um nível de 40% de transparência.

{{ image_url|watermark:"The Watermark,position=R,opacity=10,rotation=45" }}

Procura uma marca d'água com o nome "The Watermark", gera aleatoriamente uma posição para ele, a um nível de 10% de transparência, rotação de 45 graus.

{{ image_url|watermark:"w00t,opacity=40,tile=1" }}

Procura uma marca d'água chamado "w00t", telhas de TI em toda a imagem de destino inteira, a um nível de 40% de transparência.