Doist / todoist-python

DEPRECATED The official Todoist Python API library
MIT License
534 stars 73 forks source link

How to uncheck a subtree of tasks? #93

Open dluxhu opened 3 years ago

dluxhu commented 3 years ago

Is it possible to query completed subtasks of active tasks somehow? I couldn't figure it out how:

What I'd like to do is to uncheck a whole subtree of tasks given a root task id.

LloydThinks commented 3 years ago

I have written a script that does this. You need to loop through all children one at a time, and uncomplete them.

dluxhu commented 3 years ago

The problem is that in the environment I run the script (heroku), the cache is deleted regularly (daily), so I cannot see the completed children at all. On my Mac, I can see them, but only because of the caches. It is a very unpredictable behavior.

On Sun, Jan 31, 2021 at 9:01 AM Lloyd Montgomery notifications@github.com wrote:

I have written a script that does this. You need to loop through all children one at a time, and uncomplete them.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Doist/todoist-python/issues/93#issuecomment-770386914, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACJ4OM6KD6N6DIOGT346UODS4VPB3ANCNFSM4VFGK4NA .

dluxhu commented 3 years ago

Any update on this by any chance?

LloydThinks commented 3 years ago

Hey @dluxhu perhaps I can help you out here. As I said, I have already written this functionality using this Todoist Python library. I will paste the function here that does it.

def get_sub_items(self, parent_id):
    sub_items = []
    for tds_item in self.api.state['items']:
        if tds_item['parent_id'] == parent_id:
            sub_items.append(self._tds_item_to_item(tds_item))
    return sub_items

def uncomplete_item(self, item, *, recursively=True):

    # Get the raw item, and complete it
    tds_item = item.tds_item

    # Only perform the uncomplete action IF the item is checked
    if tds_item['checked']:
        tds_item.uncomplete()

    # If the user wants it, uncomplete the children and attributes as well
    if recursively:
        # Uncomplete all sub-items (if they exist and have been requested above)
        for sub_item in self.get_sub_items(tds_item['id']):
            self.uncomplete_item(sub_item, recursively=True)

    return True

My code has some helper functions that translate data, etc., but this should give you an idea.

Lloyd

dluxhu commented 3 years ago

Hi Lloyd,

Thanks for the reply! Does this code work even if you remove the local Todoist cache?

The problem is not doing the actual code itself for uncompleted, but getting the items from the API: how do you get the completed items?

I observed inconsistent behavior between my local machine and Heroku, and it turned out that my local machine cached all tasks that ever existed, but the Heroku environment is cleaned up from time to time.

I have no way of querying only the subtask tree (that includes completed and active items) of a given (active) task without using the local cache.

Correct me if I'm wrong.

Cheers,

Balázs

On Sun, Jul 18, 2021 at 10:03 AM Lloyd Montgomery @.***> wrote:

Hey @dluxhu https://github.com/dluxhu perhaps I can help you out here. As I said, I have already written this functionality using this Todoist Python library. I will paste the function here that does it.

def get_sub_items(self, parent_id): sub_items = [] for tds_item in self.api.state['items']: if tds_item['parent_id'] == parent_id: sub_items.append(self._tds_item_to_item(tds_item)) return sub_items def uncomplete_item(self, item, *, recursively=True):

# Get the raw item, and complete it
tds_item = item.tds_item

# Only perform the uncomplete action IF the item is checked
if tds_item['checked']:
    tds_item.uncomplete()

# If the user wants it, uncomplete the children and attributes as well
if recursively:
    # Uncomplete all sub-items (if they exist and have been requested above)
    for sub_item in self.get_sub_items(tds_item['id']):
        self.uncomplete_item(sub_item, recursively=True)

return True

My code has some helper functions that translate data, etc., but this should give you an idea.

Lloyd

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Doist/todoist-python/issues/93#issuecomment-882061434, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACJ4OMZMMBBUZAJ7NOSIUKLTYLNLLANCNFSM4VFGK4NA .

LloydThinks commented 3 years ago

Hi @dluxhu , you'll notice that I am searching every item to find the sub-items. This means looping over every single Todoist item in my account. Thankfully, Python is fast and I only have perhaps 1000 Todoist items at one time. Even if you have many thousands of Todoist items (what are you doing in Todoist..?), it would still be a quick operation.

As for your questions about caching and stale data. Perhaps I do not understand your use case. You can request a sync of your data 50 times a minute, which is an absurdly high and useful limit for most people. Here is the code I call before I do an operation:

api = TodoistAPI(user_api_token)
api.sync()

After this, all data is synced and correct.

Are you in need of something in addition to that?

Lloyd

dluxhu commented 3 years ago

sync() does not sync completed items, at least this is what I experienced. Try the following:

1, Create a task 2, Add two subtasks 3, Mark them completed 4, Try to uncomplete the task recursively with your code.

If you don't sync the tasks while they are active, they won't get synced to your client, so you won't be able to uncomplete them. If you did sync them, try removing the Todoist sync cache (on my Mac, it's in $HOME/.todoist-sync).

On Sun, Jul 18, 2021 at 10:48 AM Lloyd Montgomery @.***> wrote:

Hi @dluxhu https://github.com/dluxhu , you'll notice that I am searching every item to find the sub-items. This means looping over every single Todoist item in my account. Thankfully, Python is fast and I only have perhaps 1000 Todoist items at one time. Even if you have many thousands of Todoist items (what are you doing in Todoist..?), it would still be a quick operation.

As for your questions about caching and stale data. Perhaps I do not understand your use case. You can request a sync of your data 50 times a minute, which is an absurdly high and useful limit for most people. Here is the code I call before I do an operation:

api = TodoistAPI(user_api_token)api.sync()

After this, all data is synced and correct.

Are you in need of something in addition to that?

Lloyd

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Doist/todoist-python/issues/93#issuecomment-882068187, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACJ4OM5WPG55YPTPS52NVTLTYLSSFANCNFSM4VFGK4NA .

LloydThinks commented 3 years ago

I have a Heroku-based Python client that allows me to do special things with Todoist. One of them allows me to uncheck all children of a repeating task, when I complete the repeating task. I have been using this system for around 6 months, and it has not failed me yet. However, out of curiosity, I ran a test to try and replicate your issue.

Test 1

  1. Create a repeating task
  2. Add a subtask
  3. Mark repeating task (parent) complete. This of course does not actually mark the parent as complete, since it is repeating.
  4. (server automatically notices change and runs code)
  5. Child is uncompleted: Success!

Test 2

  1. Create a repeating task
  2. Add a subtask
  3. Restart my Heroku servers (dynos)
  4. Mark repeating task complete
  5. (server automatically notices change and runs code)
  6. Child is left completed: Failure...
  7. Add a new child and complete it
  8. Mark repeating task as complete again
  9. New child is uncompleted (Success!), old child is still left completed (Failure...)

It appears you have identified a bug in my code, and a limitation of the Todoist SyncAPI I was not aware of until right now. It is both frustrating that sync does not include completed tasks (for this exact reason) and a really good thing (for performance reasons).

I may not work on this code for another couple of months, so unfortunately I cannot immediately try to resolve both of our issues here. Have you looked into this to see if there is a way to force-sync completed tasks? I think there is a way based on some documentation I read awhile ago, but I cannot remember it right now.

Lloyd

dluxhu commented 3 years ago

Hi Lloyd,

What I found in the API is in the first comment of this bug, but it is not adequate.

Balázs