Bulk Uncomplete Tasks

There has been much discussion over the years about handling of recurring tasks in the app, with support not yet implemented (see: Recurring tasks is it possible? , Repeating Tasks 🔁 ).

As a workaround, specifically for daily repeating tasks, I am looking into creating a task list which holds all of my daily tasks, and marking all tasks on this list as incomplete at the start of each day.

It does not seem that there is an option to do this in the current web interface, so I am looking into alternatives. Specifically, I am looking for something that will run locally, on my Nextcloud server (Ubuntu 24.04), on a daily basis, to change all tasks in a specific list to incomplete. This could either be a server command, or a script using e.g. a command-line CalDAV client. I don’t want to depend on some other machine being on, or using some other device (i.e. solutions using Tasks.org or similar to handle recurring tasks)

Has anyone successfully done something like this, that can share a starting point, or suggest a better way of approaching such a workaround?

I have been able to successfully hack together a python script which runs on my nextcloud server once daily using cron, and can reset one or more task lists on a daily basis. One clear weakness of this method is that it relies on connecting to the server as a user, so this is not suitable for large deployments, or for use cases where passwords are not known. However, the same script should work when run from a machine other than the server, so for anyone who is a user of a nextcloud instance where they do not have any admin access, this same method should allow one to fake repeating daily tasks.

For now, I am just providing the methods I used to set this up on my machine, if there is interest, I can start a github project and do some development in hopes of supporting other deployments or use cases.

Requirements:

  • Python >= 3.4, with venv module installed system-wide
  • cron

Steps:

  1. mkdir /home/username/autoreset-tasks
    Create a folder to contain all of the relevant files. In my case, I named this folder autoreset-tasks, and put it in my home directory, but you can use any location you like. After switching to this folder ( cd /home/username/autoreset-tasks ), all other commands can run from this location.

  2. python3 -m venv venv
    Create a python virtual environment (venv) to install the required libraries, without affecting system files, or requiring root access. I named mine venv (last argument in the command above), but you can use whatever name you like

  3. ./venv/bin/pip3 install caldav-tasks-api
    Install the required library for CLI communication with a CalDAV server. See: GitHub - thiswillbeyourgithub/Caldav-Tasks-API: Python library and CLI for managing CalDAV tasks (VTODOs), supporting Nextcloud and other CalDAV servers with robust X-property handling. , caldav-tasks-api · PyPI

  4. Using the standard Nextcloud web interface, or any other interface you wish, create a new tasks list (Daily Tasksin this example). All tasks in this list will be reset daily.

  5. Create a script which will use the installed library to reset task lists. In my case, this is reset.py, given in full below. If you know Python, feel free to extend the functionality to suit your own use case.

  6. touch server.conf
    Create the associated configuration file (server.conf) which contains the credentials and task list names to be reset.

  7. chmod 600 server.conf
    Protect server.conffrom being read by other users, since it will contain plaintext credentials. Is this an ideal security method? Definitely not. However, it is in-line with nextcloud’s own security model, in which passwords for the database are stored in config.php in plaintext. And if an attacker can read a file which is read-protected and only available to a single user, they can do it as the webserver’s user as well, and all the data is compromised anyway. In my case, I don’t directly expose my nextcloud server to the internet, so I think the worry here is comparatively low

  8. Update the contents of server.conf to match the users and task lists which should reset daily. An example for the Daily Taskslist is given below.

  9. At this point, you should be able to reset all tasks on the Daily Tasks list with the command /home/username/autoreset-tasks/venv/bin/python3 /home/username/autoreset-tasks/reset.py. Test this out a couple of times by manually adding, removing, completing tasks, and verifying that everything is reset as expected.

  10. Runcrontab -eto create a cron entry for this job. To reset tasks daily, this job should run once every day at the same time. On my server, which has a 5-part cron scheduler, this looks like:

    m h  dom mon dow   command
    
    0 1 * * * /home/username/autoreset-tasks/venv/bin/python3 /home/username/autoreset-tasks/reset.py
    

    to run the task every day at 1:00 am.
    Optionally, you can add redirection to the command to use a custom log file for standard output and/or standard error.

# reset.py

import datetime

from caldav_tasks_api import TasksAPI
from caldav_tasks_api.utils.data import TaskData

class User:
    def __init__(self):
        self.name = ""
        self.pw = ""
        self.lists = []
    
    def complete(self):
        return self.name != ""
    
    def __str__(self):
        return(
            f"<User Name:'{self.name}', Pass:'********', "
            f"Lists: {len(self.lists)}: {self.lists}>"
        )
        
# Print date and time
now = datetime.datetime.now()
print(now)

# Load relevant data from config
users = []

user = User()

with open("/home/username/autoreset-tasks/server.conf", "r") as file:
    for line in file:
        # Trim whitespace, skip empty lines
        line = line.strip()
        if len(line) == 0: continue
        # Comments begin with '#' as first non-whitespace character
        if line[0] == '#': continue
        
        # Tokenize on colon. Expect exactly two tokens, as each line
        # should consist of a key-value pair
        tokens = line.split(":")
        if len(tokens) != 2:
            # Report error
            print ("Found line with " + len(tokens) + " tokens: " + line)
            # Terminate
            exit(1)
        
        key = tokens[0].strip()
        val = tokens[1].strip()
        
        # Each name entry starts a new user
        if key == "user":
            # If we have a full user, add to object
            if user.complete():
                users.append(user)
            
            # Since this starts a new user, create a new object
            user = User()
            
            user.name = val
        
        # Only one possible password, overwrite previous values
        if key == "pass":
            user.pw = val
        
        # Many possible lists, append all
        if key == "lists":
            lists = val.split(",")
            for task_list in lists:
                user.lists.append(task_list.strip())

if user.complete():
    users.append(user)

for user in users:
    print(user)

# Due to credentials, need to handle each user's task lists separately
for user in users:
    # Connect to API
    api = TasksAPI(
        url="https://localhost:8443/nextcloud/remote.php/dav/", # DAV interface
        username=user.name,                                     # From config
        password=user.pw,                                       # From config
        target_lists=user.lists,                                # Only lists which should reset daily
        ssl_verify_cert=False                                   # Needed if server is self-signed
    )
    
    # Load data from server
    api.load_remote_data()
    
    # Itereate through all lists
    for task_list in api.task_lists:
        # Iterate through all items in list
        for task in task_list.tasks:
            # Optional
            #   Check for task completion, if complete, log
             
            # Reset task status
            task.percent_complete = 0
            task.completed = False
            api.update_task(task)
            
print("Completed successfully")
# server.conf

# Comments begin with the '#' character
# Usernames, passwords, and list names  which begin or end with spaces,
# or contain the character ':' are not supported. List names also cannot contain
# the character ',' 

# User name must be given first. Use the same username as for web access
user: name
# Use the same password as for web access
pass: secret
# Multiple lists can be provided in the same entry, separated by a comma
lists: my list, my other list

# Blank lines are ignored
lists: another list
# Leading whitespace is ignored, indent as you please
  lists: list with whitespace

# Multiple users are supported. Specifying a new username starts a new
# set of lists
user: name2
lists: their list
# Password can come anywhere in the set of lists
pass: secret2

:+1: I am looking for the same thing although not on a daily basis. I use a checklist for items to pack on a business trip and find very annoying to untick 50 boxes after every trip.

So I will put your clearly explained phyton script (except cron part) on my to-do list to implement :slight_smile:

thanks!

ps: an integration in NC tasks would of course be more than welcome!