def handle_related_models(object_list, parent, clean, **kwargs):
    """ Create/find and attach all related models for a piece.
    :param object_list: The list of objects to create/find, and which
     field they belong to, in the format [{id: field_name, value: x},]
    :param parent: The object to relate to.
    :param clean: A Cleanup object.
    :kwargs
            creator: the creator of the piece, taken from request.user
            birth_date: The birth-date of a composer object
            death_date: The death-date of a composer object
    """
    for item in object_list:
        field = item.get("id")
        if field == "title":
            parent.title = item.get("value")
            continue
        if field == "composer":
            birth_date = kwargs.pop("birth_date", None)
            death_date = kwargs.pop("death_date", None)
            try:
                composer_list = abstract_model_factory(
                    item.get("value"), "Composer", clean, birth_date=birth_date, death_date=death_date
                )
                composer = composer_list[0]
                parent.composer = composer
                continue
            except:
                clean.cleanup()
                raise
        if field == "collections":
            user = kwargs.pop("user", None)
            try:
                collection_list = abstract_model_factory(item.get("value"), "Collection", clean, creator=user)
                for x in collection_list:
                    x.add(parent)
                continue
            except:
                clean.cleanup()
                raise
        if field == "languages":
            try:
                language_list = abstract_model_factory(item.get("value"), "Language", clean)
                parent.languages.clear()
                for x in language_list:
                    parent.languages.add(x)
                continue
            except:
                clean.cleanup()
                raise
        if field == "locations":
            try:
                location_list = abstract_model_factory(item.get("value"), "Location", clean)
                parent.locations.clear()
                for x in location_list:
                    parent.locations.add(x)
                continue
            except:
                clean.cleanup()
                raise
        if field == "sources":
            try:
                source_list = abstract_model_factory(item.get("value"), "Source", clean)
                parent.sources.clear()
                for x in source_list:
                    parent.sources.add(x)
                continue
            except:
                clean.cleanup()
                raise
        if field == "tags":
            try:
                tag_list = abstract_model_factory(item.get("value"), "Tag", clean)
                parent.tags.clear()
                for x in tag_list:
                    parent.tags.add(x)
                continue
            except:
                clean.cleanup()
                raise
        if field == "genres":
            try:
                genre_list = abstract_model_factory(item.get("value"), "Genre", clean)
                parent.genres.clear()
                for x in genre_list:
                    parent.genres.add(x)
                continue
            except:
                clean.cleanup()
                raise
        if field == "instruments_voices":
            try:
                ins_list = abstract_model_factory(item.get("value"), "InstrumentVoice", clean)
                parent.instruments_voices.clear()
                for x in ins_list:
                    parent.instruments_voices.add(x)
                continue
            except:
                clean.cleanup()
                raise
        if field == "number_of_voices":
            parent.number_of_voices = int(item.get("value"))
        if field == "vocalization":
            parent.vocalization = item.get("value")
        if field == "religiosity":
            parent.religiosity = item.get("value")
        if field == "composition_start_date" and item.get("value"):
            parent.composition_start_date = int(item.get("value"))
        if field == "composition_end_date" and item.get("value"):
            parent.composition_end_date = int(item.get("value"))
        if field == "comment":
            parent.comment = item.get("value")
def piece_update(request, *args, **kwargs):
    # Update a piece based on a dict of changes in request.POST['changes']
    patch_data = request.data
    form = validate_dynamic_piece_form(request, PieceForm(patch_data))
    if not form.is_valid():
        # Form errors are rendered for user on the front end. Collection
        # validation errors are ignored, as these cannot be modified from
        # the update page.
        if form.errors.get("collections"):
            del form.errors["collections"]
        if form.errors:
            errors = {f: [e.get_json_data()[0]["message"]] for f, e in form.errors.items()}
            errors = json.dumps({"errors": errors})
            return HttpResponse(content=errors, content_type="application/json", status=status.HTTP_400_BAD_REQUEST)

    clean = Cleanup()
    piece = Piece.objects.get(id=int(kwargs["pk"]))
    change = json.loads(patch_data["changes"])

    """
    Creating new movements must occur before old movements are deleted,
    as the attachment of files depends on the ordering and numbering
    of movements in the piece's present state. See comment in
    handle_dynamic_file_table() for more information.
    """
    handle_dynamic_file_table(request, piece, clean)

    modify_movements = [x for x in change["modify"] if x["type"] == "M"]
    if modify_movements:
        for item in modify_movements:
            mov = Movement.objects.filter(id=item["id"])
            if not mov:
                break
            mov = mov[0]
            if item.get("tags"):
                tag_list = abstract_model_factory(item["tags"], "Tag", clean)
                mov.tags.clear()
                for x in tag_list:
                    mov.tags.add(x)
            if item.get("instruments_voices"):
                ins_list = abstract_model_factory(item["instruments_voices"], "InstrumentVoice", clean)
                mov.instruments_voices.clear()
                for x in ins_list:
                    mov.instruments_voices.add(x)
            if item.get("comment"):
                mov.comment = item["comment"]
            if item.get("number_of_voices"):
                mov.number_of_voices = int(item["number_of_voices"])
            if item.get("vocalization"):
                mov.vocalization = item["vocalization"]
            if item.get("title"):
                mov.title = item["title"]

            mov.save()

    movement_positions = [x for x in change["modify"] if x["type"] == "M" and x.get("position")]
    movement_positions.extend([x for x in change["add"] if x["type"] == "M" and x.get("position")])
    if movement_positions:
        movements = piece.movements.all()
        for item in movement_positions:
            if item.get("id"):
                mov = movements.get(id=item["id"])
                mov.position = item["position"]
                mov.save()
            elif item.get("name"):
                mov = movements.get(title=item["name"])
                mov.position = item["position"]
                mov.save()

    modify_atts = [x for x in change["modify"] if x["type"] == "A"]
    if modify_atts:
        for item in modify_atts:
            att = Attachment.objects.filter(pk=item["id"])
            if not att:
                break
            att = att[0]
            if item.get("parent"):
                if item.get("newParentTitle") == "Attach to Piece":
                    att.movements.clear()
                    att.pieces.clear()
                    att.pieces.add(piece)
                    att.save()
                else:
                    mov = piece.movements.filter(title=item["newParentTitle"])
                    if not mov:
                        break
                    mov = mov[0]
                    att.movements.clear()
                    att.pieces.clear()
                    att.movements.add(mov)
                    att.save()
            if item.get("source"):
                att.source = item.get("source")
                att.save()

    modify_piece = [x for x in change["modify"] if x["type"] == "F"]
    if modify_piece:
        # Can use the same function as the piece-create here.
        handle_related_models(modify_piece, piece, clean)

    delete_attachments = [x for x in change["delete"] if x["type"] == "A"]
    if delete_attachments:
        for item in delete_attachments:
            att = Attachment.objects.filter(pk=item["id"])[0]
            if att:
                att.delete()

    delete_movements = [x for x in change["delete"] if x["type"] == "M"]
    if delete_movements:
        for item in delete_movements:
            mov = Movement.objects.filter(pk=item["id"])[0]
            if mov:
                mov.delete()

    piece.save()
    rebuild_suggester_dicts.delay()
    data = json.dumps({"success": True, "id": piece.id, "url": "/piece/{0}".format(piece.id)})
    return HttpResponse(data, content_type="json")