def occurrence_composition_queryset(formset, request, obj): release_id = utils_id.get_release_id(request, obj) if release_id == 0: force_formset_size(formset, 0) else: release_compositions = retrieve_release_composition(release_id) force_formset_size(formset, len(release_compositions)) ## Does not help with saving the emtpy forms (only with their initial values)... #formset.validate_min = True #formset.validate_max = True # Would not be a safe approach, see comment in populate_occurrence_attributes() #formset.initial = [{"release_composition": compo} for compo in release_compositions] release_compositions = list(release_compositions) for form in formset: release_compo, in_db = get_or_initial_release_corresponding_entry(form, release_compositions, "release_composition") release = release_compo.to_release #form.empty_permitted=False ## Does not help with empty forms either form.fields["to_occurrence"].queryset = ( Occurrence.objects.filter(release=release) # only propose occurrences of the right release .filter(Q(occurrence_composition__isnull=True) # not already nested in another occurrence | Q(occurrence_composition__from_occurrence=formset.instance)) # except if nested in this occurrence (for edit) ) form.fields["to_occurrence"].label = "Nested {} occurrence".format(release.name)
def populate_occurrence_attributes(formset, request, obj): release_id = utils_id.get_release_id(request, obj) attributes = retrieve_noncustom_custom_release_attributes(release_id) force_formset_size(formset, len(attributes)) # We had to move away from always assigning the initial value for release_corresponding_entry: # When "changing" and existing occurrence, if the Occurrence***Attribute are not saved in the same order as the Release***Attribute of the matching Release # (side note: this situation is treated as an error by formset clean) # the value assigned to initial would override the release_corresponding_entry saved in the DB, # but the associated attribute value would still be the one from the DB, causing a mismatch. #formset.initial = [{"release_corresponding_entry": attrib} for attrib in attributes] # Instead, we create a list of all Release***Attribute assigned to the correponding release, and remove from this list # the Release***Attribute which already have a matchin Occurrence***Attribute saved to the DB. attribute_list = list(attributes) for form in formset: rel_attrib, in_db = get_or_initial_release_corresponding_entry(form, attribute_list, "release_corresponding_entry") # This is very important: by default, forms in formsets have empty_permitted set to True # Then, a form with no other value than the initial(s) would skip fields validation, not populating cleaned_data # see: https://github.com/django/django/blob/1.8.3/django/forms/forms.py#L389 form.empty_permitted=False form.fields["value"] = enum.Attribute.Type.to_form_field[rel_attrib.attribute.value_type] if not in_db: form.initial.update({ "attribute_type": ContentType.objects.get_for_model(rel_attrib.__class__), "attribute_id": rel_attrib.pk, })
def populate_occurrence_attributes(formset, request, obj): release_id = utils_id.get_release_id(request, obj) attributes = retrieve_noncustom_custom_release_attributes(release_id) force_formset_size(formset, len(attributes)) # We had to move away from always assigning the initial value for release_corresponding_entry: # When "changing" and existing occurrence, if the Occurrence***Attribute are not saved in the same order as the Release***Attribute of the matching Release # (side note: this situation is treated as an error by formset clean) # the value assigned to initial would override the release_corresponding_entry saved in the DB, # but the associated attribute value would still be the one from the DB, causing a mismatch. #formset.initial = [{"release_corresponding_entry": attrib} for attrib in attributes] # Instead, we create a list of all Release***Attribute assigned to the correponding release, and remove from this list # the Release***Attribute which already have a matchin Occurrence***Attribute saved to the DB. attribute_list = list(attributes) for form in formset: rel_attrib, in_db = get_or_initial_release_corresponding_entry( form, attribute_list, "release_corresponding_entry") # This is very important: by default, forms in formsets have empty_permitted set to True # Then, a form with no other value than the initial(s) would skip fields validation, not populating cleaned_data # see: https://github.com/django/django/blob/1.8.3/django/forms/forms.py#L389 form.empty_permitted = False form.fields["value"] = enum.Attribute.Type.to_form_field[ rel_attrib.attribute.value_type] if not in_db: form.initial.update({ "attribute_type": ContentType.objects.get_for_model(rel_attrib.__class__), "attribute_id": rel_attrib.pk, })
def populate_occurrence_picture_attributes_choices(formset, request, obj): """ Populates the any_attribute ChoiceField of OccurrencePictureForm with all attributes (incl. CustomReleaseAttributes) available on the Release of the related occurrence. Also populates its initial data for edition form. """ release_id = utils_id.get_release_id(request, obj) choices = [("", "----")] attributes = utils.retrieve_noncustom_custom_release_attributes(release_id) choices.extend([ ("{}_{}".format(ContentType.objects.get_for_model(attribute.__class__).pk, attribute.pk), "{}".format(attribute),) for attribute in attributes]) for form in formset: form.fields["any_attribute"].choices = choices if form.instance.attribute_id: form.fields["any_attribute"].initial = "{}_{}".format(form.instance.attribute_type.pk, form.instance.attribute_id) def empty_form(self): form = super(OccurrencePictureFormSet, self).empty_form form.fields["any_attribute"].choices = choices return form # The empty_formset property is used when clicking the "Add another Occurrence picture" in the web interface # "patches" the property by subclassing, as described on SO: http://stackoverflow.com/a/31591589/1027706 class PatchedFormset(formset.__class__): @property def empty_form(self): form = super(PatchedFormset, self).empty_form form.fields["any_attribute"].choices = choices return form formset.__class__ = PatchedFormset
def occurrence_composition_queryset(formset, request, obj): release_id = utils_id.get_release_id(request, obj) if release_id == 0: force_formset_size(formset, 0) else: release_compositions = retrieve_release_composition(release_id) force_formset_size(formset, len(release_compositions)) ## Does not help with saving the emtpy forms (only with their initial values)... #formset.validate_min = True #formset.validate_max = True # Would not be a safe approach, see comment in populate_occurrence_attributes() #formset.initial = [{"release_composition": compo} for compo in release_compositions] release_compositions = list(release_compositions) for form in formset: release_compo, in_db = get_or_initial_release_corresponding_entry( form, release_compositions, "release_composition") release = release_compo.to_release #form.empty_permitted=False ## Does not help with empty forms either form.fields["to_occurrence"].queryset = ( Occurrence.objects.filter( release=release ) # only propose occurrences of the right release .filter( Q(occurrence_composition__isnull=True ) # not already nested in another occurrence | Q(occurrence_composition__from_occurrence=formset.instance) ) # except if nested in this occurrence (for edit) ) form.fields["to_occurrence"].label = "Nested {} occurrence".format( release.name)