Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

1.1K
Vistas
Django: CreateView no guarda el formulario con el formset anidado

Estoy tratando de adaptar un enfoque para guardar conjuntos de formularios anidados con el formulario principal usando 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é muy agradecido si alguien puede señalar mi error. También necesito agregar tres líneas en la misma vista para EmployeeForm. Probé Django-Extra-Views pero no pude hacer que funcionara. Le agradecería que me aconsejara agregar más de una línea para la misma vista, como alrededor de 5. Todo lo que quiero lograr es una sola página para crear Employee y sus líneas como Education, Experience, Others . A continuación se muestra 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)

Vista:

 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})

Formularios:

 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 según el 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.

over 4 years ago · Santiago Trujillo
3 Respuestas
Responde la pregunta

0

Actualmente no está procesando el formset 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 el método de post , y allí debe validar tanto el formulario como los conjuntos de formularios adjuntos:

 def post(self, request, *args, **kwargs): form = self.get_form() # Add as many formsets here as you want education_formset = EducationFormset(request.POST) # Now validate both the form and any formsets if form.is_valid() and education_formset.is_valid(): # Note - we are passing the education_formset to form_valid. If you had more formsets # you would pass these as well. return self.form_valid(form, education_formset) else: return self.form_invalid(form)

Luego modificas form_valid así:

 def form_valid(self, form, education_formset): with transaction.atomic(): form.instance.employee.user = self.request.user self.object = form.save() # Now we process the education formset educations = education_formset.save(commit=False) for education in educations: education.instance = self.object education.save() # If you had more formsets, you would accept additional arguments and # process them as with the one above. # Don't call the super() method here - you will end up saving the form twice. Instead handle the redirect yourself. return HttpResponseRedirect(self.get_success_url())

La forma en que actualmente está usando 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 tu método form_valid() . En su lugar, debe pasar el conjunto de formularios a este método desde el método post() como se describe anteriormente.

He dejado algunos comentarios adicionales en el código de muestra anterior que, con suerte, lo ayudarán a resolver esto.

over 4 years ago · Santiago Trujillo Denunciar

0

Tal vez le gustaría ver el paquete django-extra-views , que proporciona la vista CreateWithInlinesView , que le permite crear formularios con líneas anidadas como Django-admin inlines.

En tu caso sería algo así:

vistas.py

 class EducationInline(InlineFormSetFactory): model = Education fields = ['course_title', 'institute_name'] class EmployeeCreateView(CreateWithInlinesView): model = Employee inlines = [EducationInline,] fields = ['about', 'street', 'city', 'cell_phone', 'landline'] template_name = 'bars/crt.html'

crt.html

 <form method="post"> ... {{ form }} <table> {% for formset in inlines %} {{ formset.management_form }} {% for inline_form in formset %} <tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}"> {{ inline_form }} </tr> {% endfor %} {% endfor %} </table> ... <input type="submit" value="Submit" /> </form> <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"> {% for formset in inlines %} $('.formset_row-{{ formset.prefix }}').formset({ addText: 'add another', deleteText: 'remove', prefix: '{{ formset.prefix }}', }); {% endfor %} </script>

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 visitar la documentación para más información

EDITADO: agregué management_form y los botones js para agregar/eliminar.

over 4 years ago · Santiago Trujillo Denunciar

0

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 django sobre cómo funcionan los conjuntos de formularios en línea, tendrá problemas.

Bien, 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 la ID del objeto principal para colocarlo como clave externa. Cambiar el atributo de instancia de un atributo de formulario después de init no es una buena idea.

En formas normales, si lo cambia después de is_valid, tendrá resultados impredecibles. Para los conjuntos de formularios, cambiar el atributo de instancia incluso directamente después de init no hará nada, porque 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 formset, establezca solo el archivo 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 inicialice todos los formularios/conjuntos de formularios a la vez. Cuando se llama al método is_valid(), todas las formas 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 complicada es que después de inicializar su formulario principal, debe inicializar los otros (formsests) con la instancia de su primer formulario. También debe configurar 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. Esto separará la lógica comercial de la lógica de vista porque desde la perspectiva de la vista solo tiene un formulario que puede ser:

inicializado con algunos datos (en este caso, datos POST) cuya validez se verifica con is_valid() se puede guardar con save() cuando es válido. Conservas la interfaz del formulario y si creaste tu formulario correctamente, incluso puedes usarlo no solo para crear sino también para actualizar objetos junto con sus objetos relacionados y las vistas serán muy simples.

over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda