Estou tentando adaptar uma abordagem para salvar conjuntos de formulários aninhados com o formulário principal usando o recurso de layout Django-Crispy-Forms, mas não consigo salvá-lo. Estou seguindo este projeto de exemplo de código, mas não foi possível validar o formset para salvar dados. Ficarei muito agradecido se alguém puder apontar meu erro. Também preciso adicionar três linhas na mesma exibição para EmployeeForm. Eu tentei o Django-Extra-Views, mas não consegui fazer esse trabalho. Apreciaria se você aconselhase a adição de mais de uma linha para a mesma exibição como em torno de 5. Tudo o que eu quero é que uma única página para criação Employee
e suas linhas como Education, Experience, Others
. Abaixo está o código:
modelos:
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employees',
null=True, blank=True)
about = models.TextField()
street = models.CharField(max_length=200)
city = models.CharField(max_length=200)
country = models.CharField(max_length=200)
cell_phone = models.PositiveIntegerField()
landline = models.PositiveIntegerField()
def __str__(self):
return '{} {}'.format(self.id, self.user)
def get_absolute_url(self):
return reverse('bars:create', kwargs={'pk':self.pk})
class Education(models.Model):
employee = models.ForeignKey('Employee', on_delete=models.CASCADE, related_name='education')
course_title = models.CharField(max_length=100, null=True, blank=True)
institute_name = models.CharField(max_length=200, null=True, blank=True)
start_year = models.DateTimeField(null=True, blank=True)
end_year = models.DateTimeField(null=True, blank=True)
def __str__(self):
return '{} {}'.format(self.employee, self.course_title)
Visão:
class EmployeeCreateView(CreateView):
model = Employee
template_name = 'bars/crt.html'
form_class = EmployeeForm
success_url = None
def get_context_data(self, **kwargs):
data = super(EmployeeCreateView, self).get_context_data(**kwargs)
if self.request.POST:
data['education'] = EducationFormset(self.request.POST)
else:
data['education'] = EducationFormset()
print('This is context data {}'.format(data))
return data
def form_valid(self, form):
context = self.get_context_data()
education = context['education']
print('This is Education {}'.format(education))
with transaction.atomic():
form.instance.employee.user = self.request.user
self.object = form.save()
if education.is_valid():
education.save(commit=False)
education.instance = self.object
education.save()
return super(EmployeeCreateView, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('bars:detail', kwargs={'pk':self.object.pk})
Formulários:
class EducationForm(forms.ModelForm):
class Meta:
model = Education
exclude = ()
EducationFormset =inlineformset_factory(
Employee, Education, form=EducationForm,
fields=['course_title', 'institute_name'], extra=1,can_delete=True
)
class EmployeeForm(forms.ModelForm):
class Meta:
model = Employee
exclude = ('user', 'role')
def __init__(self, *args, **kwargs):
super(EmployeeForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-md-3 create-label'
self.helper.field_class = 'col-md-9'
self.helper.layout = Layout(
Div(
Field('about'),
Field('street'),
Field('city'),
Field('cell_phone'),
Field('landline'),
Fieldset('Add Education',
Formset('education')),
HTML("<br>"),
ButtonHolder(Submit('submit', 'save')),
)
)
Objeto de layout personalizado, como por exemplo:
from crispy_forms.layout import LayoutObject, TEMPLATE_PACK
from django.shortcuts import render
from django.template.loader import render_to_string
class Formset(LayoutObject):
template = "bars/formset.html"
def __init__(self, formset_name_in_context, template=None):
self.formset_name_in_context = formset_name_in_context
self.fields = []
if template:
self.template = template
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
formset = context[self.formset_name_in_context]
return render_to_string(self.template, {'formset': formset})
Formset.html:
{% load static %}
{% load crispy_forms_tags %}
{% load staticfiles %}
<table>
{{ formset.management_form|crispy }}
{% for form in formset.forms %}
<tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field|as_crispy_field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<br>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
<script src="{% static 'js/jquery.formset.js' %}">
</script>
<script type="text/javascript">
$('.formset_row-{{ formset.prefix }}').formset({
addText: 'add another',
deleteText: 'remove',
prefix: '{{ formset.prefix }}',
});
</script>
Não há erros no terminal e / ou não. A ajuda é muito apreciada.
fonte
Respostas:
No momento, você não está processando o formset corretamente no seu
CreateView
.form_valid
nessa visão, manipulará apenas o formulário pai, não os conjuntos de formulários. O que você deve fazer é substituir opost
método e é necessário validar o formulário e quaisquer conjuntos de formulários anexados a ele:Então você modifica
form_valid
assim:A maneira como você está usando no momento
get_context_data()
não está correta - remova esse método completamente. Só deve ser usado para buscar dados de contexto para renderizar um modelo. Você não deve chamá-lo do seuform_valid()
método. Em vez disso, você precisa passar o formset para esse método a partir dopost()
método descrito acima.Deixei alguns comentários adicionais no código de exemplo acima, que esperamos ajudar você a descobrir isso.
fonte
Talvez você queira ver o pacote
django-extra-views
, o fornece a viewCreateWithInlinesView
, pois permite criar formulários com linhas aninhadas, como as linhas do Django-admin.No seu caso, seria algo assim:
views.py
crt.html
A view
EmployeeCreateView
processará os formulários para você como no Django-admin. A partir deste ponto, você pode aplicar o estilo que deseja aos formulários.Eu recomendo que você visite a documentação para obter mais informações
EDITADO: Eu adicionei
management_form
e os botões js para adicionar / remover.fonte
management_form
para cadaformset
Você disse que há um erro, mas não o está mostrando na sua pergunta. O erro (e todo o rastreamento) é mais importante do que qualquer coisa que você escreveu (exceto pode ser de forms.py e views.py)
Seu caso é um pouco mais complicado por causa dos conjuntos de formulários e do uso de vários formulários no mesmo CreateView. Não há muitos (ou não muitos bons) exemplos na internet. Até você digitar no código django como os formulários embutidos estão funcionando, você terá problemas.
Ok, direto ao ponto. Seu problema é que os conjuntos de formulários não são inicializados com a mesma instância do seu formulário principal. E quando seu formulário amin salva os dados no banco de dados, a instância no conjunto de formulários não é alterada e, no final, você não possui o ID do objeto principal para ser colocado como chave estrangeira. Alterar o atributo de instância de um atributo de formulário após o init não é uma boa ideia.
Em formas normais, se você alterá-lo após is_valid, terá resultados imprevisíveis. Para os conjuntos de formulários, a alteração do atributo da instância, mesmo diretamente após o init, não será alterada, pois os formulários no formset já foram inicializados com alguma instância e alterá-lo após não ajudará. A boa notícia é que você pode alterar os atributos da instância após a inicialização do Formset, porque todos os atributos da instância do formulário apontarão para o mesmo objeto após a inicialização do formset.
Você tem duas opções:
Em vez de definir o atributo da instância se o formset, defina apenas o instance.pk. (Isso é apenas um palpite: eu nunca faço isso, mas acho que deve funcionar. O problema é que ele parecerá hackeado). Crie um formulário que inicialize todos os formulários / conjuntos de formulários de uma só vez. Quando o método is_valid () é chamado, todos os fomrs devem ser validados. Quando o método save () é chamado, todos os formulários devem ser salvos. Em seguida, você precisa definir o atributo form_class do seu CreateView para essa classe de formulário. A única parte complicada é que, após a inicialização do formulário principal, você precisa inicializar os outros (testes de formulários) com a instância do seu primeiro formulário. Você também precisa definir os formulários / conjuntos de formulários como atributos do seu formulário para ter acesso a eles no modelo. Estou usando a segunda abordagem quando preciso criar um objeto com todos os objetos relacionados.
inicializado com alguns dados (neste caso, dados POST) verificados quanto à validade com is_valid () pode ser salvo com save () quando é válido. Você preserva a interface do formulário e, se você o criou corretamente, pode usá-lo não apenas para criar, mas também para atualizar objetos junto com seus objetos relacionados, e as visualizações serão muito simples.
fonte