Ich versuche, einen Ansatz zum Speichern verschachtelter Formularsätze mit dem Hauptformular mithilfe der Layoutfunktion Django-Crispy-Forms anzupassen, kann ihn jedoch nicht speichern. Ich verfolge dieses Codebeispielprojekt, konnte jedoch das Formularset nicht validieren, um Daten zu speichern. Ich werde wirklich dankbar sein, wenn jemand auf meinen Fehler hinweisen kann. Ich muss auch drei Inlines in derselben Ansicht für EmployeeForm hinzufügen. Ich habe Django-Extra-Views ausprobiert, konnte das aber nicht zum Laufen bringen. Würde mich freuen, wenn Sie empfehlen, mehr als eine Inline für dieselbe Ansicht wie etwa 5 hinzuzufügen. Alles, was ich erreichen möchte, ist eine einzelne Seite zum Erstellen Employee
und deren Inlines wie Education, Experience, Others
. Unten ist der Code:
Modelle:
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)
Aussicht:
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})
Formen:
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')),
)
)
Benutzerdefiniertes Layoutobjekt wie im Beispiel:
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>
Es gibt keine Fehler im Terminal und / oder auf andere Weise. Hilfe wird sehr geschätzt.
quelle
Antworten:
Sie verarbeiten das Formularset derzeit nicht ordnungsgemäß in Ihrem
CreateView
.form_valid
In dieser Ansicht wird nur das übergeordnete Formular behandelt, nicht die Formularsätze. Was Sie tun sollten, ist diepost
Methode zu überschreiben , und dort müssen Sie sowohl das Formular als auch alle damit verbundenen Formularsätze überprüfen:Dann ändern Sie
form_valid
wie folgt:Die Art und Weise, wie Sie sie derzeit verwenden,
get_context_data()
ist nicht korrekt. Entfernen Sie diese Methode vollständig. Es sollte nur zum Abrufen von Kontextdaten zum Rendern einer Vorlage verwendet werden. Sie sollten es nicht von Ihrerform_valid()
Methode aus aufrufen . Stattdessen müssen Sie das Formularset von der oben beschriebenenpost()
Methode an diese Methode übergeben .Ich habe im obigen Beispielcode einige zusätzliche Kommentare hinterlassen, die Ihnen hoffentlich dabei helfen werden, dies herauszufinden.
quelle
Vielleicht möchten Sie das Paket sehen
django-extra-views
, das die Ansicht bietetCreateWithInlinesView
, mit der Sie Formulare mit verschachtelten Inlines wie Django-Admin-Inlines erstellen können.In Ihrem Fall wäre es ungefähr so:
views.py
crt.html
Die Ansicht
EmployeeCreateView
verarbeitet die Formulare für Sie wie in Django-admin. Ab diesem Punkt können Sie den gewünschten Stil auf die Formulare anwenden.Ich empfehle Ihnen, die Dokumentation zu besuchen , um weitere Informationen zu erhalten
BEARBEITET: Ich habe hinzugefügt
management_form
und die js-Schaltflächen hinzugefügt / entfernt.quelle
management_form
für jeden hinzufügenformset
Sie haben gesagt, dass ein Fehler vorliegt, aber Sie zeigen ihn in Ihrer Frage nicht an. Der Fehler (und der gesamte Traceback) ist wichtiger als alles, was Sie geschrieben haben (außer möglicherweise von forms.py und views.py).
Ihr Fall ist aufgrund der Formularsätze und der Verwendung mehrerer Formulare in derselben CreateView etwas schwieriger. Es gibt nicht viele (oder nicht viele gute) Beispiele im Internet. Bis Sie im Django-Code nachlesen, wie Inline-Formsets funktionieren, werden Sie Probleme haben.
Ok direkt auf den Punkt. Ihr Problem ist, dass Formelsätze nicht mit derselben Instanz wie Ihr Hauptformular initialisiert werden. Und wenn Ihr Amin-Formular die Daten in der Datenbank speichert, wird die Instanz im Formularsatz nicht geändert und am Ende haben Sie nicht die ID des Hauptobjekts, um als Fremdschlüssel abgelegt zu werden. Das Ändern des Instanzattributs eines Formularattributs nach init ist keine gute Idee.
In normalen Formen haben Sie unvorhersehbare Ergebnisse, wenn Sie es nach is_valid ändern. Bei Formularsätzen ist das Ändern des Instanzattributs auch direkt nach init nicht ausreichend, da die Formulare im Formularsatz bereits mit einer Instanz initialisiert wurden und das Ändern nach dem Formularsatz nicht hilft. Die gute Nachricht ist, dass Sie die Attribute der Instanz nach der Initialisierung von Formset ändern können, da alle Instanzattribute von Forms nach der Initialisierung von Formset auf dasselbe Objekt verweisen.
Sie haben zwei Möglichkeiten:
Anstatt das Instanzattribut für das Formset festzulegen, legen Sie nur die Instanz.pk fest. (Dies ist nur eine Vermutung, dass ich es nie getan habe, aber ich denke, es sollte funktionieren. Das Problem ist, dass es als Hack aussehen wird). Erstellen Sie ein Formular, das alle Formulare / Formularsätze gleichzeitig initialisiert. Wenn die Methode is_valid () aufgerufen wird, sollten alle fomrs validiert werden. Wenn die Methode save () aufgerufen wird, müssen alle Formulare gespeichert werden. Dann müssen Sie das Attribut form_class Ihrer CreateView auf diese Formularklasse setzen. Der einzige schwierige Teil ist, dass Sie nach der Initialisierung Ihres Hauptformulars die anderen (Formsests) mit der Instanz Ihres ersten Formulars initialisieren müssen. Außerdem müssen Sie die Formulare / Formularsätze als Attribute Ihres Formulars festlegen, um in der Vorlage darauf zugreifen zu können. Ich verwende den zweiten Ansatz, wenn ich ein Objekt mit allen zugehörigen Objekten erstellen muss.
initialisiert mit einigen Daten (in diesem Fall POST-Daten), die mit is_valid () auf Gültigkeit geprüft wurden, können mit save () gespeichert werden, wenn sie gültig sind. Sie behalten die Formularoberfläche bei und wenn Sie Ihr Formular korrekt erstellt haben, können Sie es sogar nicht nur zum Erstellen, sondern auch zum Aktualisieren von Objekten zusammen mit den zugehörigen Objekten verwenden. Die Ansichten sind sehr einfach.
quelle