Я новичок в Django и пытаюсь внедрить систему отслеживания изменений, которая позволяет пользователям назначать типы изменений, причины и комментарии к любым изменениям, которые происходят в экземплярах элементов класса модели Django.Я смоделировал свою проблему после этого примера https://github.com/philgyford/django-nested-inline-formsets-example У меня есть модель FK-иерархии Item -> ItemHistory -> Item Change Reasons (может быть несколько причин для каждого изменения).Я хотел бы видеть все изменения для данного элемента и все причины и комментарии для всех изменений для этого элемента на той же странице.По типу и причине изменения я реализовал зависимый раскрывающийся список с помощью JQuery - набор запросов по причинам изменения зависит от выбора типа изменения.Проблема, с которой я сталкиваюсь, заключается в отображении сохраненных типов изменений и причин изменений в представлении обновлений.Я пытаюсь передать различные наборы запросов в форму ChangeReasonsForm, которые реализованы как вложенные поля в наборе форм BaseVersionReasonsFormset.Я смог передать наборы запросов из представления в BaseVersionReasonsFormset, но не могу понять, как передать его во вложенные формы.Заранее благодарны за любую помощь и предложения!
Forms.py
class ChangeReasonsForm(forms.ModelForm):
change_reason = forms.ModelChoiceField(queryset=ChangeReason.objects.none())
class Meta:
model = ChangeReasons
fields = ('change_type_id', 'change_reason', 'comment')
def __init__(self, *args, **kwargs):
qs_dict = kwargs.pop('change_reasons')
index = kwargs.pop('index')
qs = list(qs_dict.values())[index]
super(ChangeReasonsForm, self).__init__(*args, **kwargs)
self.fields['change_reason'].queryset = ChangeReason.objects.none()
self.fields['change_type_id'].empty_label = ""
self.fields['change_reason'].empty_label = ""
self.helper = FormHelper(self)
if self.instance.pk:
self.fields['change_reason'].queryset = qs
ChangeReasonsFormSet = inlineformset_factory(
ItemHistoryJoiner,
ChangeReasons,
form=ChangeReasonsForm,
extra=1,
can_delete=True,
)
class BaseVersionReasonsFormset(BaseInlineFormSet):
"""
The base formset for editing Changes belonging to a Item, and the
Reasons belonging to those Changes.
"""
def get_form_kwargs(self, index):
kwargs = super(BaseVersionReasonsFormset, self).get_form_kwargs(index)
change_reasons_list = kwargs.pop('change_reasons_list')
print('index: {}'.format(index))
print('reasons list: {}'.format(change_reasons_list))
print('list index i: {}'.format(change_reasons_list[index]))
self.change_reasons = change_reasons_list[index]
print('self.change_reasons: {}'.format(self.change_reasons))
return kwargs
def add_fields(self, form, index):
super().add_fields(form, index)
# Save the formset for a Change's Reasons in the nested property.
form.nested = ChangeReasonsFormSet(
instance=form.instance,
data=form.data if form.is_bound else None,
files=form.files if form.is_bound else None,
prefix='change_reason-%s-%s' % (
form.prefix,
ChangeReasonsFormSet.get_default_prefix()
),
form_kwargs={'change_reasons': self.change_reasons,
'index': index,
},
)
def is_valid(self):
"""
Also validate the nested formsets.
"""
result = super().is_valid()
if self.is_bound:
for form in self.forms:
if hasattr(form, 'nested'):
result = result and form.nested.is_valid()
return result
def clean(self):
"""
If a parent form has no data, but its nested forms do, we should
return an error, because we can't save the parent.
For example, if the Change form is empty, but there are Reasons.
"""
super().clean()
for form in self.forms:
if not hasattr(form, 'nested') or self._should_delete_form(form):
continue
if self._is_adding_nested_inlines_to_empty_form(form):
form.add_error(
field=None,
error=('You are trying to add reason(s) to a change which '
'does not yet exist. Please add information '
'about the change and specify the reason(s) again.'))
def save(self, commit=True):
"""
Also save the nested formsets.
"""
result = super().save(commit=commit)
for form in self.forms:
if hasattr(form, 'nested'):
if not self._should_delete_form(form):
form.nested.save(commit=commit)
return result
def _is_adding_nested_inlines_to_empty_form(self, form):
"""
Are we trying to add data in nested inlines to a form that has no data?
e.g. Adding reasons to a new version whose data we haven't entered?
"""
if not hasattr(form, 'nested'):
# A basic form; it has no nested forms to check.
return False
if is_form_persisted(form):
# We're editing (not adding) an existing model.
return False
if not is_empty_form(form):
# The form has errors, or it contains valid data.
return False
# All the inline forms that aren't being deleted:
non_deleted_forms = set(form.nested.forms).difference(
set(form.nested.deleted_forms)
)
# At this point we know that the "form" is empty.
# In all the inline forms that aren't being deleted, are there any that
# contain data? Return True if so.
return any(not is_empty_form(nested_form) for nested_form in non_deleted_forms)
# This is the formset for the versions belonging to a Item and the
# reasons belonging to those versions.
# You'd use this by passing in a Item:
VersionsWithCommentsFormset = inlineformset_factory(
Item,
ItemHistory,
formset=BaseVersionReasonsFormset,
# We need to specify at least one ItemHistory field:
fields=('version',),
widgets={
'version': forms.HiddenInput()
},
extra=0,
max_num=0,
# If you don't want to be able to delete Items:
can_delete=False,
)
views.py
class ChangesUpdateView(SingleObjectMixin, FormView):
"""
For assigning reasons and comments to changes
"""
model = Item
template_name = 'app/Item_changes_update.html'
def get(self, request, *args, **kwargs):
# The Item we're editing:
self.object = self.get_object(queryset=Item.objects.all())
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
# The Publisher we're uploading for:
self.object = self.get_object(queryset=Item.objects.all())
return super().post(request, *args, **kwargs)
def get_form(self, form_class=None):
"""
Use our big formset of formsets, and pass in the Item object.
"""
item = self.object
item_history_set = item.history_set.all()
version_reasons_dict = OrderedDict({'version_id_{}'.format(version.version_id):
{'comment_id_{}'.format(
comment.pk): comment.change_type_id.change_reasons.all()
for comment in version.reasons.all()
}
for version in item_history_set
})
version_reasons_list = list(version_reasons_dict.values())
version_comment_formset = VersionsWithCommentsFormset(
**self.get_form_kwargs(),
instance=self.object,
form_kwargs={'change_reasons_list': version_reasons_list}
)
return version_comment_formset
def form_valid(self, form):
"""
If the form is valid, redirect to the supplied URL.
"""
# if self.request.POST.get('submit'):
if form.is_valid():
form.save()
messages.add_message(
self.request,
messages.SUCCESS,
'Changes were successfully saved.'
)
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('app:change_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the historical versions
context['item_history'] = get_item_history(self.object)
return context