Copying one to-do list to another

Hi folks,

I’m trying to copy one to-do list to another, but have run into an issue. When copying to the second list, all the separate items from the 1st list end up all together as a single item on the 2nd. I’ll include my code so far, below. Anyone able to help out?

alias: Test 2
description: ""
triggers:
  - trigger: state
    entity_id:
      - input_boolean.test_toggle
    from: "off"
    to: "on"
conditions: []
actions:
  - action: todo.get_items
    metadata: {}
    data:
      status:
        - needs_action
    target:
      entity_id:
        - todo.shopping_list
    response_variable: items
  - action: todo.add_item
    metadata: {}
    data:
      item: >-
        {%- for item in items['todo.shopping_list']['items'] %}  {{ item.summary
        ~ "\n" }} {%- endfor %} 
    target:
      entity_id: todo.real_shopping_list
mode: single

the input list looks like this;

However the output list looks like this;

Any help would be appreciated thanks.

You need to used a Repeat for Each action to run each of the items in your template results through the todo.add_item action.

alias: Test 2
description: ""
triggers:
  - trigger: state
    entity_id:
      - input_boolean.test_toggle
    from: "off"
    to: "on"
conditions:
  - condition: numeric_state
    entity_id: todo.shopping_list
    above: 0
actions:
  - action: todo.get_items
    metadata: {}
    data:
      status:
        - needs_action
    target:
      entity_id:
        - todo.shopping_list
    response_variable: items
  - repeat:
      for_each: |
        {{ items['todo.shopping_list']['items'] | map(attribute='summary') | list }}
      sequence:
        - action: todo.add_item
          metadata: {}
          data:
            item: "{{ repeat.item }}" 
          target:
            entity_id: todo.real_shopping_list
mode: single
1 Like

Okay, that’s working, thank you. Do you have any idea how to prevent duplicating on the target list, if the automation is run more than once? For instance, if I add another few items to the source list, and then sync the to the target list again, it adds the entire source list to the target list again. I’m hoping that I can do get it to just sync newly added items.

You would need to add “todo.real_shopping_list” to the entity id list in todo.get_items, then use the difference() function so that what goes through the repeat is only the items that are in the base list but not the “real” list.

{% set real = items['todo.real_shopping_list']['items']  | map(attribute='summary') | list %}
{% set base = items['todo.shopping_list']['items'] | map(attribute='summary') | list %}
{{ difference(base, real) }}

That assumes the duplicate is in needs_action, if you want to check all items and change them from completed to not completed, that gets a bit more complicated… but it’s do-able. It would probably be easiest to do as two separate loops.

Ultimately, I want to be able to do both the needs action and completed parts to sync in both directions. It’s a limitation of the shopping list integration not having a widget, but the to-do lists having one, and the shopping list integration having native support for voice, but the to-do lists needing custom intents. TBH I think it’s a bit of a mess and I can see it being resolved at some point, but currently I need the work around.
Can you walk me through where in the automation the additional code would need to go?

Here’s a rough draft… there are probably a couple things that could be made more efficient, but this should get you started:

alias: Working List items to Real Shopping List
description: ""
triggers:
  - trigger: state
    entity_id:
      - input_boolean.test_toggle
    from: "off"
    to: "on"
conditions:
  - condition: numeric_state
    entity_id: todo.shopping_list
    above: 0
actions:
  - alias: Get NEEDS_ACTION from Working List
    action: todo.get_items
    metadata: {}
    data:
      status: needs_action
    response_variable: working
    target:
      entity_id: todo.shopping_list
  - action: todo.get_items
    metadata: {}
    data: {}
    response_variable: shopping
    target:
      entity_id: todo.real_shopping_list
    alias: Get ALL items from Real Shopping List
  - variables:
      working_sums: >
        {{ working['todo.shopping_list']['items'] | map(attribute='summary') | list }}
      real_all_items: |
        {{ shopping['todo.real_shopping_list']['items'] }}
      real_needs_sums: |
        {{ real_all_items | selectattr('status', 'eq', 'needs_action') | map(attribute='summary') | list }}
      real_completed_sums_to_change: |
        {{ real_all_items | selectattr('status', 'eq', 'completed') | selectattr('summary', 'in', working_sums) 
        | map(attribute='summary') | list }}
  - repeat:
      for_each: "{{real_completed_sums_to_change}}"
      sequence:
        - action: todo.update_item
          data:
            status: needs_action
            item: "{{repeat.item}}"
          target:
            entity_id: todo.real_shopping_list
    alias: Change Completed to Needs Action
  - delay: 1
  - repeat:
      for_each: >
        {# Combine summaries from incomplete items from Real list with the ones that were changed #}
        {% set real = real_needs_sums + real_completed_sums_to_change %}
        {# Return the summaries from the Working list that still need to be added to the Real list#}
        {{ difference(working_sums, real) }}
      sequence:
        - action: todo.add_item
          metadata: {}
          data:
            item: "{{ repeat.item }}"
          target:
            entity_id: todo.real_shopping_list
mode: single

You could optionally add further actions in the repeat to remove or “check off” items from the working list. If you want, you can add either of the following after the last action, making sure that it is properly indented to be within the repeat’s sequence:

#Remove
- action: todo.remove_item
  data:
    item: "{{repeat.item}}"
  target:
    entity_id: todo.shopping_list

#Mark as completed
- action: todo.update_item
  data:
    status: completed
    item: "{{repeat.item}}"
  target:
    entity_id: todo.shopping_list

Holy shit, thanks man, that looks good. I’m only getting back online now, since yesterday, so I’ll try it out, but thanks again for the assist.