Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python

David Dzsotjan
David Dzsotjan
6,405 Points

To-do list app in Python

Inspired by the diary app in the Databases in Python course of Kenneth Love, I've made a to-do list app. It lets you add and delete entries, modify them, as well as flag them 'DONE'. There is a handy cleanup feature that gets rid of all completed (DONE) entries that are more than a week old, unless they are flagged 'Protected'. Please feel free to make suggestions and comments!:)

"""To-do list where you can chronologically add your tasks, modify them and mark if they have been completed.
  A cleanup feature enables you to delete completed tasks which are more than a week old - unless
  you have flagged them as 'protected'."""
from collections import OrderedDict
import datetime
import os

from peewee import *

db = SqliteDatabase('to_do_list.db')


class ToDo(Model):
    """Model for creating to-do items. 'done' indicates that it's been completed,
    'protected' makes it immune to cleanup"""
    task = CharField(max_length=255)
    timestamp = DateTimeField(default=datetime.datetime.now)
    done = BooleanField(default=False)
    protected = BooleanField(default=False)

    class Meta:
        database = db


def clear():
    """Clear the display"""
    os.system('cls' if os.name == 'nt' else 'clear')


def initialize():
    """Connect to database, build tables if they don't exist"""
    db.connect()
    db.create_tables([ToDo], safe=True)


def view_entries(index, entries, single_entry):
    """"View to-do list"""
    clear()

    index = index % len(entries)  # determines which entry is selected for modification
    if single_entry:  # to see only 1 entry
        entries = [entries[index]]
        index = 0
    else:
        print('\nMY TO-DO LIST')
        print('=' * 40)
    prev_timestamp = None

    for ind, entry in enumerate(entries):
        timestamp = entry.timestamp.strftime('%d/%B/%Y')

        if timestamp != prev_timestamp:  # same timestamps get printed only once
            print('\n')
            print(timestamp)
            print('=' * len(timestamp))
            prev_timestamp = timestamp

        if ind == index:  # placing the selection tick
            tick = '> '
        else:
            tick = '  '

        print('{}{}'.format(tick, entry.task), end='')
        if entry.done:
            print('\t(DONE)', end='')
        if entry.protected:
            print('\t <P>', end='')
        print('')

    return entries  # so that we can modify the given entry if needed


def add_entry(index, entries):
    """Add a new task"""

    new_task = input('\nTo do: ')
    if input('Protect [yN]? ').lower().strip() == 'y':
        protect = True
    else:
        protect = False
    ToDo.create(task=new_task,
                protected=protect)


def modify_entry(index, entries):
    """Modify selected entry"""
    entry = view_entries(index, entries, True)[0]
    print('\n\n')

    for key, value in sub_menu.items():
        print('{}) {}'.format(key, sub_menu[key].__doc__))
    print('q) Back to Main')
    next_action = input('Action: ')

    if next_action.lower().strip() in sub_menu:
        sub_menu[next_action](entry)
    else:
        return


def cleanup_entries(index, entries):
    """Cleanup: delete completed, non-protected entries older than a week"""
    if (input('Have you checked that you protected the important stuff? [yN]').lower().strip() == 'y'):
        now = datetime.datetime.now()
        for entry in entries:
            if (now - entry.timestamp > datetime.timedelta(7, 0, 0) and entry.done and not entry.protected):
                entry.delete_instance()


def modify_task(entry):
    """Modify task"""
    new_task = input('> ')
    entry.task = new_task
    entry.save()


def delete_entry(entry):
    """Erase entry"""
    if (input('Are you sure [yN]? ').lower().strip() == 'y'):
        entry.delete_instance()


def toggle_done(entry):
    """Toggle 'DONE'"""
    entry.done = not entry.done
    entry.save()


def toggle_protection(entry):
    """Toggle 'protected'"""
    entry.protected = not entry.protected
    entry.save()


def menu_loop():
    choice = None
    index = 0  # shows which entry is selected
    entries = ToDo.select().order_by(ToDo.timestamp.asc())
    while choice != 'q':
        if len(entries) != 0:
            view_entries(index, entries, False)

            print('\n' + '=' * 40 + '\n')
            print('Previous/Next: p/n \n')
        for key, value in main_menu.items():
            print('{}) {}'.format(key, value.__doc__))
        print('q) Quit')

        choice = input('\nAction: ')
        if choice in main_menu:
            try:
                main_menu[choice](index, entries)
            except ZeroDivisionError:
                continue
            entries = ToDo.select().order_by(ToDo.timestamp.asc())  # update entries after operations

        elif choice == 'n':
            index += 1
        elif choice == 'p':
            index -= 1


main_menu = OrderedDict([
    ('a', add_entry),
    ('m', modify_entry),
    ('c', cleanup_entries)
])

sub_menu = OrderedDict([
    ('m', modify_task),
    ('d', toggle_done),
    ('p', toggle_protection),
    ('e', delete_entry)
])

if __name__ == '__main__':
    initialize()
    menu_loop()
    db.close()

1 Answer

very cool, trying to do the same thing. already created a program that takes links i found interesting on the web and stores them. you can tag by keyword and search for keywords to find links quickly.

one problem i have encountered, let me know how you solved it: when running the program from the terminal (another directory) i have to specify the absolute filepath. is there any solution to not hardcore said path to locate the db?