Mostrando postagens com marcador Inlines. Mostrar todas as postagens
Mostrando postagens com marcador Inlines. Mostrar todas as postagens

quarta-feira, 2 de setembro de 2015

Desmistificando Forms: CreateView e UpdateView com o trabalho pesado


O básico do CreateView

Começando do básico, o exemplo abaixo já quebra um galho enorme quando de trata de formulários simples, como por exemplo o Contato de algum site.

forms.py
# coding: utf-8
from django import forms

from .models import Contato

class ContatoForm(forms.ModelForm):
 nome = forms.CharField(widget=forms.TextInput(attrs={'class' : 'required',}), label="Nome")
 email = forms.EmailField(widget=forms.TextInput(attrs={'class' : 'required',}), label="E-mail")
 texto = forms.CharField(widget=forms.Textarea(attrs={'class' : 'required',}), label="Texto")


 class Meta:
  model = Contato
  fields = '__all__'

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

Até aqui sem muitas dificuldades né? Vemos a definição da classe para gerenciamento do form, com alguns atributos. Com o fields = '__all__' não era necessário definir os atributos, mas isso é obrigatório caso queira adicionar uma classe, como por exemplo o required ou outro atributo qualquer, por exemplo um placeholder, etc.

views.py
# Create your views here.
# coding: utf-8
from django.views.generic.edit import CreateView

from .forms import ContatoForm
from .models import Contato


class ContatoView(CreateView):
 form_class = ContatoForm
 success_url = '/contato/sucesso/'
 template_name = 'contato/contato.html'
 model = Contato

 def get_context_data(self, **kwargs):
  kwargs.update({
   'menu':'contato',
   'title': 'Contato',
  })
  return kwargs

A view também é bem tranquila. Herdando as características da CreateView, exige apenas algumas informações para resolver o problema de inserção e validação dos campos de forma elegante:

  • form_class: Classe do Form definida no forms.py
  • success_url: URL para qual será redirecionado após o sucesso da inserção
  • template_name: Html do form.
  • model: Classe definida no models.py

contato.html
<form method="post" action="">
    {% csrf_token %}
    {{ form.errors}}
    {{ form.as_p }}
    <input class="btn" type="submit" value="Enviar">
</form>

Acima temos um exemplo bem sucinto de como montar um form de forma bem automágica e podemos observar algumas coisas interessantes com o exemplo acima:

  • A CreateView trabalha com o post para própria página (mesma url) por isso no action do form não tem nenhuma informação. 
  • A validação do CSRF já é nativa da CreateView, a unica coisa que precisa e incluir o token no html dentro da tag form.
  • {{ form.errors }} vai gerar uma <ul> com a tag errorlist onde cada <li> será responsável por lista os erros de um campo específico e terá uma <ul> também com a classe errorlist uma lista de <li> para todos os erros deste respectivo campo.
Outra coisa legal de fazer é definir no html os campos separamente para um tratamento de erro e layout melhor apresentados. Isso pode ser feito assim:

<p>{{ form.email }}</p>
<div class="error">{{ form.email.errors }}</div>


E o UpdatView?

Um formulário de contato não sofrerá update bem possivelmente. Mas apenas para ilustar usaremos o e mesmo exemplo.


views.py
class ContatoUpdateView(UpdateView):
 form_class = ContatoForm
 model = Contato
 success_url = '/contato/sucesso/'
 template_name =  'contato/contato.html'

 def get_context_data(self, **kwargs):
  kwargs.update({
   'menu':'contato',
   'title': 'Contato',
   'update':True,
  })
  return kwargs

Como assim só isso? Pois é. Só isso. E sim o HTML é exatemente o mesmo. Se usar algum recurso para gerar o form como o {{ form.as_p }}, {{ form.as_table }} é só isso e o mesmo html acima. Caso defina os campos individualmente, lembre-se de colocar {{ form.id }} para validar a instancia que está sendo editada.


Eu já mostrei como fazer para utilizar os inlines junto com a CreateView neste post: http://djangoweb.blogspot.com.br/2013/08/como-utilizar-os-inlines-no-front-para.html. Agora vamos a algo mais divertido. 

Que tal criarmos um exemplo mais complexo, onde não terá apenas uma, mas sim várias classes inline, com o CreateView e UpdateView?


Multiplos inlines


forms.py
# coding: utf-8
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.models import inlineformset_factory

from django import forms
from .models import *

from util import util as U

class ModeloForm(forms.ModelForm):
 
 class Meta:
  model = Modelo
  fields = '__all__'

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


class ModeloInline1Form(forms.ModelForm):

 class Meta:
  model = ModeloInline1
  fields = '__all__'

ModeloInline1FormSet = inlineformset_factory(Modelo, ModeloInline1, extra=0, min_num=1, form=ModeloInline1Form, fields='__all__')

class ModeloInline2Form(forms.ModelForm):

 class Meta:
  model = ModeloInline1
  fields = '__all__'

ModeloInline2FormSet = inlineformset_factory(Modelo, ModeloInline2, extra=0, min_num=1, form=ModeloInline1Form, fields='__all__')


views.py
class ModeloCreateView(CreateView):
 form_class = ModeloForm
 success_url = '/home/'
 template_name = 'modelo/form.html'
 model = Modelo

 def get(self, request, *args, **kwargs):
  self.object = None
  form_class = self.get_form_class()
  form = self.get_form(form_class)
  inline1_form = ModeloInline1FormSet()
  inline2_form = ModeloInline2FormSet()
  return self.render_to_response(self.get_context_data(form=form, inline1_form = inline1_form , inline2_form = inline2_form ))
 def post(self, request, *args, **kwargs):
  self.object = None
  form_class = self.get_form_class()
  form = self.get_form(form_class)
  inline1_form = ModeloInline1FormSet(self.request.POST)
  inline2_form = ModeloInline1FormSet(self.request.POST)
  if (form.is_valid() and inline1_form .is_valid() and
   inline2_form .is_valid()):
   return self.form_valid(form, inline1_form , inline2_form )
  else:
   return self.form_invalid(form, inline1_form , inline2_form )

 def form_valid(self, form, inline1_form, inline2_form ):
  self.object = form.save()
  inline1_form.instance = self.object
  inline1_form.save()
  inline2_form.instance = self.object
  inline2_form.save()
  return HttpResponseRedirect(self.get_success_url())

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


 def get_context_data(self, **kwargs):
  kwargs.update({})
  return kwargs


class ModeloUpdateView(UpdateView):
 form_class = ModeloForm
 model = Modelo
 success_url = '/home/'
 template_name =  'modelo/form.html'

 def get(self, request, *args, **kwargs):
  self.object = self.get_object()
  form_class = self.get_form_class()
  form = self.get_form(form_class)

  # Render form
  inline1_form = ModeloInline1FormSet(instance=self.object)
  inline2_form = ModeloInline2FormSet(instance=self.object)
  return self.render_to_response(self.get_context_data(form=form,inline1_form=inline1_form,inline2_form=inline2_form))

 def post(self, request, *args, **kwargs):
  self.object = self.get_object()
  form_class = self.get_form_class()
  form = self.get_form(form_class)
  inline1_form = ModeloInline1FormSet(self.request.POST, instance=self.object)
  inline2_form = ModeloInline2FormSet(self.request.POST, instance=self.object)
  if (form.is_valid() and inline1_form.is_valid() and inline2_form.is_valid()):
   return self.form_valid(form, inline1_form, inline2_form)
  else:
   return self.form_invalid(form, inline1_form, inline2_form)

 def form_valid(self, form, inline1_form , inline2_form ):
  self.object = form.save()
  inline1_form.instance = self.object
  inline1_form.save()
  inline2_form.instance = self.object
  inline2_form.save()
  return HttpResponseRedirect(self.get_success_url())

 def form_invalid(self, form, inline1_form , inline2_form ):

  return self.render_to_response(
   self.get_context_data(
    form=form,
    inline1_form=inline1_form,
    inline2_form=inline2_form)
   )

 def get_context_data(self, **kwargs):
  kwargs.update({
   'update':True,
  })
  return kwargs

Nos exemplos acima, o formulário html pode ser o mesmo como no exemplo do Contato. Podemos também utilizar o recurso do inline visto no post cima citado para inserção de quantos inlines forem necessários com o mesmo recurso do admin para sempre adicionar mais um conforme necessidade.

Um jeito mais elegante de usar isso é usar um template vinculado ao js, com o plugin underscore disponível aqui: http://underscorejs.org/

<script type="text/javascript" src="/static/site/js/plugins/underscore/underscore-min.js"></script>
<script type="text/html" id="modeloinline1-template">
  <div class="bloco-modeloinline1 clearfix">
   <div class="col-lg-12">
    <div class="form-group">
     <label for="">Exemplo de Textarea</label>
     <textarea rows="5" name="modeloinline1_set-<%= id %>-descricao" id="id_modeloinline1_set-<%= id %>-descricao" cols="40" class="form-control"></textarea>
    </div>
   </div>
  </div>
 </script>

django.jQuery('.container-modeloinline1').on('click', '.btn-adicionar-modeloinline1', function(ev) {
    ev.preventDefault();
    django.jQuery(this).parent().find('button').hide();
    var count = django.jQuery('.container-modeloinline1').children().length;
    var tmplMarkup = django.jQuery('#modeloinline1-template').html();
    var compiledTmpl = _.template(tmplMarkup, { id : count });
    django.jQuery('.modeloinline1-form"').append(compiledTmpl);
    // update form count
    django.jQuery('#id_modeloinline1_set-TOTAL_FORMS').attr('value', count+1);
   });
</script>
<form role="form" action="" method="post" class=''>

 {{ modeloinline1.management_form }}
 <div class="container-modeloinline1-form">
 {% for form in inline1_form %}
  {% if update %}
   {{ form.id }}
   {{ form.avaliacao }}
  {% endif %}
  {% if form.errors %}
       <div class="alert alert-danger">
                     Por favor corriga os erros abaixo para prosseguir:
                     {{ form.errors}}
               </div>
         {% endif %}
 </div>
</form>
<button class="btn btn-primary btn-sm btn-adicionar-modeloinline1" type="button">Adicionar outro Modeloinline1</button>

sexta-feira, 23 de agosto de 2013

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!

sexta-feira, 24 de maio de 2013

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

Para isso é preciso de dois models, um model Pai que conterá N instancias de objetos de uma classe Filho, como por exemplo a relação de Conteúdo para Fotos. Assumindo este exemplo, deixe os arquivos como a seguir:

models.py
from django.db import models
# Create your models here.
class Conteudo(models.Model):
 """(Conteudo description)"""

 # ...
 # SEUS ATRIBUTOS AQUI
 # ...

 class Meta:
  verbose_name, verbose_name_plural = u"Conteúdo" , u"Conteúdos"


class Foto(models.Model):
 """(Foto description)"""
 conteudo= models.ForeignKey(Conteudo, verbose_name=u'Conteúdo')
 imagem = models.ImageField(upload_to="uploads/conteudos/imagem/%Y")

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

 def __unicode__(self):
  return "%s" % str(self.imagem)

forms.py
from django.forms.models import inlineformset_factory
from models import Conteudo, Foto
class ConteudoForm(forms.ModelForm):
 class Meta:
  model = Conteudo

FotoFormSet = inlineformset_factory(Conteudo, Foto, extra=1)


views.py
from models import *
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template import Context, loader, RequestContext
from django.core.context_processors import csrf
from forms import ConteudoForm, FotoFormSet

def conteudo_enviar(request):
 ret = ''
 if request.POST:
  c = {}
  c.update(csrf(request))

  r = request.POST
  f = request.FILES
  form = ConteudoForm(r,f)
  formset = FotoFormSet(r,f)

  if form.is_valid() and request.FILES:
   try:
    form.save()
    formset.instance = get_object_or_404(Conteudo, id=form.instance.id)
    formset.save()
    ret = u'Conteúdo enviado com sucesso.'

    # ZERANDO O FORM
    form = ConteudoForm()
    formset = FotoFormSet()

   except:
    ret = u'Erro ao enviar o conteúdo'
  elif request.FILES:
   for field in form:
    if field.errors:
     ret += "%s: %s" % (field.label, striptags(str(field.errors)) )
  else:
   ret += "Imagem: pelo menos uma imagem do conteúdo deve ser enviada"
 else:
  form = ConteudoForm()
  formset = FotoFormSet()

 VARS = {
  'form':form,
  'formset':formset,
  'ret':ret,
 }
 return render_to_response('conteudo_template.html', VARS, context_instance=RequestContext(request))

conteudo_template.html

<script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
<script type="text/html" id="foto-template">
 <div id="foto-<%= id %>" class="imagem">
  <input id="id_foto_set-<%= id %>-id" type="hidden" name="foto_set-<%= id %>-id">
     <label for="id_foto-<%= id %>-title">Imagem:</label>
     <input id="id_foto_set-<%= id %>-imagem" class="file" type="file" name="foto_set-<%= id %>-imagem">
     <input type="hidden" name="foto-<%= id %>-acao" id="id_foto-<%= id %>-acao">
     <input type="hidden" name="foto-<%= id %>-id" id="id_foto-<%= id %>-id">
     {% if not request.META.HTTP_USER_AGENT|isIE %}
   <div class="filename"></div>
   <div class="button-container">
   <input type="button" name="filebutton" class="filebutton" value="Procurar">
   </div>
  {% endif %}
  </div>
</script>

<script type="text/javascript">
jQuery(document).ready(function($) {
  // Stuff to do as soon as the DOM is ready;
  $('.add-foto').click(function(ev){
  ev.preventDefault();
  var count = $('.fotos').children().length;
  var tmplMarkup = $('#foto-template').html();
  var compiledTmpl = _.template(tmplMarkup, { id : count });
  $('div.fotos').append(compiledTmpl);
  // update form count
  $('#id_foto_set-TOTAL_FORMS').attr('value', count+1);
  console.log(count+1);
 });
});
</script>
{{ formset.management_form }}
<div class="fotos">
 {% for foto_form in formset %}
  <div id="foto-{{ forloop.counter0 }}" class="imagem">
   {{ foto_form.id }}
   {% if request.META.HTTP_USER_AGENT|isIE %}
    {# IE HACK PARA INPUT FILE #}
    {{ foto_form }}
    {# <input type="file" id="id_imagem" name="imagem[]" class="file" style="right:0; height: 50px; width:478px; position:absolute;opacity:1.0;filter:alpha(opacity=100);"> #}
   {% else %}
    {{ foto_form }}
    <div class="filename"></div>
    <div class="button-container">
     <input type="button" name="filebutton" class="filebutton" value="Procurar">
    </div>
   {% endif %}
  </div>
 {% endfor %}
</div>
<div class="form-actions">
  <button class="medium green add-foto">Adicionar mais fotos</button>
</div>

Para as class-based views, mostrarei como fazer no próximo post. hasta!