J'essaie d'adapter une approche pour enregistrer des ensembles de formulaires imbriqués avec le formulaire principal à l'aide de la fonctionnalité de mise en page Django-Crispy-Forms, mais je ne peux pas l'enregistrer. Je suis en train de suivre cet exemple de projet, mais je n'ai pas pu valider formset pour enregistrer les données. Je serai vraiment reconnaissant si quelqu'un peut signaler mon erreur. J'ai également besoin d'ajouter trois inlines dans la même vue pour EmployeeForm. J'ai essayé Django-Extra-Views mais je n'ai pas pu faire fonctionner ça. J'apprécierais si vous conseillez d'ajouter plus d'une ligne pour la même vue, comme environ 5. Tout ce que je veux, c'est une seule page pour la création Employee
et ses lignes comme Education, Experience, Others
. Voici le code:
des modèles:
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)
Vue:
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})
Formes:
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')),
)
)
Objet de mise en page personnalisé selon l'exemple:
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>
Il n'y a aucune erreur dans le terminal et / ou autrement. L'aide est très appréciée.
la source
Réponses:
Vous ne traitez pas actuellement correctement l'ensemble de formulaires dans votre
CreateView
.form_valid
dans cette vue, ne traitera que le formulaire parent, pas les jeux de formulaires. Ce que vous devez faire est de remplacer lapost
méthode, et là, vous devez valider le formulaire et tous les jeux de formulaires qui lui sont attachés:Ensuite, vous modifiez
form_valid
comme suit:La façon dont vous utilisez actuellement
get_context_data()
n'est pas correcte - supprimez complètement cette méthode. Il ne doit être utilisé que pour récupérer des données de contexte pour le rendu d'un modèle. Vous ne devez pas l'appeler depuis votreform_valid()
méthode. Au lieu de cela, vous devez passer le formset à cette méthode à partir de lapost()
méthode décrite ci-dessus.J'ai laissé quelques commentaires supplémentaires dans l'exemple de code ci-dessus qui, je l'espère, vous aideront à comprendre cela.
la source
Peut-être que vous aimeriez voir le package
django-extra-views
, le fournit la vueCreateWithInlinesView
, qui vous permet de créer un formulaire avec des inlines imbriquées comme Django-admin inlines.Dans votre cas, ce serait quelque chose comme ça:
views.py
crt.html
La vue
EmployeeCreateView
traitera les formulaires pour vous comme dans Django-admin. À partir de là, vous pouvez appliquer le style souhaité aux formulaires.Je vous recommande de visiter la documentation pour plus d'informations
EDITÉ: j'ai ajouté
management_form
et les boutons js pour ajouter / supprimer.la source
management_form
pour chacunformset
Vous avez dit qu'il y avait une erreur mais vous ne la montrez pas dans votre question. L'erreur (et toute la trace) est plus importante que tout ce que vous avez écrit (sauf qu'elle peut provenir de forms.py et views.py)
Votre cas est un peu plus délicat en raison des jeux de formulaires et de l'utilisation de plusieurs formulaires sur le même CreateView. Il n'y a pas beaucoup (ou pas beaucoup de bons) exemples sur Internet. Jusqu'à ce que vous creusiez dans le code django comment fonctionnent les jeux de formulaires en ligne, vous aurez des problèmes.
Ok droit au but. Votre problème est que les jeux de formulaires ne sont pas initialisés avec la même instance que votre formulaire principal. Et lorsque votre formulaire amin enregistre les données dans la base de données, l'instance du jeu de formulaires n'est pas modifiée et, à la fin, vous n'avez pas l'ID de l'objet principal pour être mis en tant que clé étrangère. Changer l'attribut d'instance d'un attribut de formulaire après init n'est pas une bonne idée.
Dans des formes normales, si vous le modifiez après is_valid, vous obtiendrez des résultats imprévisibles. Pour les jeux de formulaires, changer l'attribut d'instance même directement après init ne fera rien, car les formulaires dans le jeu de formulaires sont déjà initialisés avec une instance, et le changer après n'aidera pas. La bonne nouvelle est que vous pouvez modifier les attributs de l'instance après l'initialisation de Formset, car tous les attributs d'instance de formulaires pointeront vers le même objet après l'initialisation de formset.
Vous avez deux options:
Au lieu de définir l'attribut d'instance si le formset, définissez uniquement l'instance.pk. (C'est juste une supposition que je n'ai jamais fait, mais je pense que cela devrait fonctionner. Le problème est qu'il ressemblera à du hack). Créez un formulaire qui initialisera tous les formulaires / jeux de formulaires à la fois. Quand c'est la méthode is_valid () qui est appelée, tous les fomrs doivent être validés. Quand sa méthode save () est appelée, tous les formulaires doivent être enregistrés. Ensuite, vous devez définir l'attribut form_class de votre CreateView sur cette classe de formulaire. La seule partie délicate est qu'après l'initialisation de votre formulaire principal, vous devez initialiser les autres (formulaires) avec l'instance de votre premier formulaire. Vous devez également définir les formulaires / jeux de formulaires en tant qu'attributs de votre formulaire afin d'y avoir accès dans le modèle. J'utilise la deuxième approche lorsque j'ai besoin de créer un objet avec tous ses objets associés.
initialisé avec certaines données (dans ce cas données POST) dont la validité est vérifiée avec is_valid () peut être enregistré avec save () lorsqu'il est valide. Vous conservez l'interface du formulaire et si vous avez créé votre formulaire correctement, vous pouvez même l'utiliser non seulement pour créer mais aussi pour mettre à jour des objets avec leurs objets associés et les vues seront très simples.
la source