Estoy tratando de adaptar un enfoque para guardar conjuntos de formularios anidados con el formulario principal utilizando la función de diseño Django-Crispy-Forms, pero no puedo guardarlo. Estoy siguiendo este proyecto de ejemplo de código, pero no pude validar el conjunto de formularios para guardar datos. Estaré realmente agradecido si alguien puede señalar mi error. También necesito agregar tres líneas en la misma vista para EmployeeForm. Intenté Django-Extra-Views pero no pude hacer que eso funcionara. Le agradecería si me aconseja agregar más de una línea para la misma vista, como alrededor de 5. Todo lo que quiero es lograr una sola página para crear Employee
y sus líneas como Education, Experience, Others
. Debajo está el 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)
Ver:
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})
Formas:
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 diseño personalizado como por ejemplo:
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>
No hay errores en la terminal y / o de otra manera. La ayuda es muy apreciada.
fuente
Respuestas:
Actualmente no está procesando el formulario correctamente en su
CreateView
.form_valid
en esa vista solo manejará el formulario principal, no los conjuntos de formularios. Lo que debe hacer es anular elpost
método, y allí debe validar tanto el formulario como los conjuntos de formularios adjuntos:Luego modificas
form_valid
así:La forma en que está utilizando actualmente
get_context_data()
no es correcta: elimine ese método por completo. Solo debe usarse para obtener datos de contexto para representar una plantilla. No deberías llamarlo desde tuform_valid()
método. En su lugar, debe pasar el conjunto de formularios a este método desde elpost()
método descrito anteriormente.Dejé algunos comentarios adicionales en el código de muestra anterior que esperamos lo ayuden a resolver esto.
fuente
Tal vez le gustaría ver el paquete
django-extra-views
, que proporciona la vistaCreateWithInlinesView
, que le permite crear formularios con líneas anidadas como las líneas de Django-admin.En su caso, sería algo así:
views.py
crt.html
La vista
EmployeeCreateView
procesará los formularios por usted como en Django-admin. Desde este punto, puede aplicar el estilo que desee a los formularios.Te recomiendo que visites la documentación para más información.
EDITADO: agregué
management_form
y los botones js para agregar / eliminar.fuente
management_form
para cada unoformset
Dijiste que hay un error pero no lo estás mostrando en tu pregunta. El error (y todo el rastreo) es más importante que cualquier cosa que haya escrito (excepto que puede ser de forms.py y views.py)
Su caso es un poco más complicado debido a los conjuntos de formularios y al uso de múltiples formularios en el mismo CreateView. No hay muchos (o no muchos buenos) ejemplos en Internet. Hasta que profundice en el código de django cómo funcionan los conjuntos de formularios en línea, tendrá problemas.
Ok directo al grano. Su problema es que los conjuntos de formularios no se inicializan con la misma instancia que su formulario principal. Y cuando su formulario amin guarda los datos en la base de datos, la instancia en el conjunto de formularios no cambia y al final no tiene el ID del objeto principal para poder colocarlo como clave foránea. Cambiar el atributo de instancia de un atributo de formulario después de init no es una buena idea.
En formas normales, si lo intercambia después de is_valid, obtendrá resultados impredecibles. Para los conjuntos de formularios, el cambio del atributo de instancia incluso directamente después de que init no funcione, ya que los formularios en el conjunto de formularios ya están inicializados con alguna instancia, y cambiarlo después no ayudará. La buena noticia es que puede cambiar los atributos de la instancia después de inicializar Formset, porque todos los atributos de instancia de los formularios apuntarán al mismo objeto después de inicializar formset.
Tienes dos opciones:
En lugar de establecer el atributo de instancia si el conjunto de formularios, establece solo el instance.pk. (Esto es solo una suposición, nunca lo he hecho, pero creo que debería funcionar. El problema es que se verá como un truco). Cree un formulario que inicializará todos los formularios / conjuntos de formularios a la vez. Cuando se llama al método is_valid (), todos los formularios deben validarse. Cuando se llama al método save (), todos los formularios deben guardarse. Luego, debe establecer el atributo form_class de su CreateView en esa clase de formulario. La única parte difícil es que después de que se inicializa su formulario principal, debe inicializar los otros (conjuntos de formularios) con la instancia de su primer formulario. También debe establecer los formularios / conjuntos de formularios como atributos de su formulario para tener acceso a ellos en la plantilla. Estoy usando el segundo enfoque cuando necesito crear un objeto con todos sus objetos relacionados.
inicializado con algunos datos (en este caso, datos POST) verificados para verificar su validez con is_valid () se puede guardar con save () cuando es válido. Conserva la interfaz del formulario y, si realizó el formulario correctamente, incluso puede usarlo no solo para crear sino para actualizar objetos junto con sus objetos relacionados y las vistas serán muy simples.
fuente