📝 How to Make a Todo List! [Tutorial + Template]
h
Bookie0

Hi!!!

Welcome to a tutorial/template on how to make a Todo List Organizer in Python! Originally for @Vandesm14's Jam (sorry for ping lol), I've modified it a bit so that it's a tad simpler and fit for a tutorial/template.


How it'll look like

Here are some snapshots:

This shall be the home page or main menu:

Making a new task:

Completing a task:


Features of the Todo List

The Todo List shall have the following features:

  • Make a new task

  • Edit tasks

  • View tasks

  • Display how many total tasks (and how many are completed/un-completed)

  • Delete tasks

  • Complete tasks

  • Un-complete tasks

  • A full box ((▣)) to indicate completedness

  • An empty box ((☐)) to indicate un-completedness


Outline of Code

First of all, let's start with a basic outline, or summary of the code.

We'll need something to hold all the tasks. At first thought, we could use a list and add all the tasks to do inside. However, we also want to assign to each task either an empty box ((☐)) or a full one ((▣)) to indiate that it is not completed or completed, respectfully. So instead of using a list, we'll use a dict, todo_name_done (with the "name" of task as key, and the "doneness", or "completedness" as value. Hence the name). This is how it'll look like:

To count and keep track of how many tasks are completed or not completed, this time we could use arrays. We shall have one list holding tasks that are completed, completed_tasks and another holding tasks that aren't completed, not_completed_tasks. Finally, there's also the variable no_tasks which we'll use to determine if the user has 0 tasks (more on that later).

Also by the way, don't worry about noting down those lists, dicts, and variable. I'll say it again below for the full tutorial/template!

So the different key features that I mentioned above will be in a function, like to view your tasks, to make a new task, to complete/un-complete a task, etc. so that we can use it several times. Then we'll also have the main_menu() function show the things you can do as well as the main input and conditionals which will determine what the user wants to do.

The very last will have a while True loop with the function calling the main menu and yea that's when everything happens.

Also, I'll be showing the code step by step, and I may sometimes have the previous step's code in the current one. I will seperate what's new and old with ### NEW ###. And when I add ..., it means I skipped a bit of the code.


The Tutorial (Finally!)

Ok, so first, like I explained above, we'l have the dicts, lists, and variable:


view_tasks() Function

Next, let's start with one of the features we'll use several times; to view all your tasks.

We'll put it in a function called view_tasks():

In that function, we'll output some useful numbers, like the total number of tasks, the number of completed tasks, and the number of un-completed tasks. To find out the numbers, we shall use len() to get the length of items in the lists/dicts. This will return an int, and to concatenate it successfully in the print() statement, we convert it to str. At the end, you'll notice another print statement that will output "Your Tasks" with a few \n for new lines (for some space in between):

Now, to actually output each task, we'll have a for loop that shall loop through each item in the dict todo_name_done and output the key and it's value. We'll be using enumerate that numbers each item it loops through, starting from 0. That's why instead of a usual for i in (todo_name_done):, we'll have for n, i in enumerate(todo_name_done): The n will be the variable for enumerate, and the i shall be the variable holding the current key in the dict todo_name_done.

Next, we'll have some conditionals. Now, the conditionals are not needed because this tutorial will only give you the basic program, meaning no colors or fancy design (thought I'll link a repl for that as well). However, if you do use colors later on, that small branch of conditionals shall be useful. First, here's the code:

Let's break it up. First, we have the if statement: if todo_name_done[i] == "(▣)":. This checks if the current task in the for loop is equal to the full square; it basically checks if that task is completed. Remember this is how our dict is shaped:

The task name is the key, and the empty/full square is the value. And because the for loop loops through the dict's keys, to get the value of a key you do name_of_dict[key_name]. So in this case, to get the value (the square) of a task name (the key), we do todo_name_done[i]. And i is task name that the for loop is currently on. It's the same thing with the elif, except that it checks if the task is not completed (the empty square).

Both branches of the conditionals output the same thing: print(str(n+1) + ") " + todo_name_done[i] + " " + i). The only thing is that you could insert colors inside the print statement; so for example, in the if statement, you could color that print green to indicate that the following task is completed. In the elif statement, you could color the print red to indicate that the following task is not completed. I'll be using ANSI escape codes but I'll cover how exactly to implement the colors somewhere, later.

Back on that print; using concatenation again, it first outputs the n+1 (converted into str), because remember, enumerate starts at 0, and we don't wwant the first task to be labeled as 0. Then, it outputs todo_name_done[i] which gets the value of the key (the empty/full squares). Finally, it outputs the key (the name of the task); i.

Wooh, you've done it! We can now view tasks. This is how it will look like:

You can see that we have 4 tasks, as said from the total number of tasks, and we also have 2 completed tasks and 2 un-completed tasks, again indicated above. Then, with the for loop it prints each task, numbered, with either a full square if it's completed and a empty square if it's not.

But how did those tasks end up there? And how do you make a task? That's what we shall cover next!


new_task() Function

The new_task() function will be the function holding the code for, well, making a new task! So first, let's define the function and indicate that this is to make a new task:

Now when you make a task, the program should ask the user to enter a name for the task. For that, we'll use an input().

But say you already have this task name, what if the user makes another new task with that same name? That would kind of mess up/break the program, so we should use conditionals to determine if what the user wrote is already used. Also, we'll need a while True: loop that will loop contineously as long as the user enters a name for the task that is already used. Once they enter a new name, we'll use break to "break" the loop, or get out of it, and continue with the program. Otherwise, we'll use pass to "pass", or to continue with the loop we're in.

The expression to determine if the task name is already used will be somethign like this: if something in something:. We'll just have to replace those 2 "something"s!

This is how it'll look like:

So first, we have the input(), and we store what the user says in the variable new_task_name. Then, using an if statement, we check if the name of that new task, new_task_name is already in the dict holding all the tasks, todo_name_done. If this expression evaluates to True, then the code inside that if statement shall execute; the program shall tell the user (with a print() statement) that they already have a task with the same name. Then, we use pass to kind of "pass" and carry on with the loop.

Now, let's add an else statement so that when the user enters a valid name for the task (a name not the same to any other existing tasks), they can continue with the rest of the program. In this else statement, we'll have quite a few things.

The most important parts will be actually creating the new task; meaning that we add it inside the dict as a key, and add the empty square as value. This is because when you initially create a task, the task is by default not completed yet, that's why we automatically put the value as an empty square.

The second most important thing is to add this new task to the not_completed_tasks; which, if you recall, is the list holding all the not completed tasks. We'll use append() to add it to the list.

Finally, we'll have a break, which will cause the program to exit the while True: loop and carry on with the rest of the program.

You shall all notice that I'll add some other small pieces of code; those aren't really neccessary, but make the program a bit cooler. I'll explain them a bit more in detail in the code with comments and also after this code block:

As you can see, we've added the new important parts I've talked about above, and also a few extra things:

First, there's a little print() statement that says "Creating...", and the program "sleeps", or waits, 0.4 seconds before continuing. Right after, with os.system('clear'), it clears the console screen. This makes a cool kind of effect. Then there's a little message of confirmation affirm that the task was created. The program waits 1.3 seconds , and then you see break at the end which makes the program exit the loop and continue with the rest of the program.

All those parts are just for design and small, cool, effects, and like I said, they are not needed. You can change how long the program waits and what the print() statement exactly says; it's completly up to you!

Great job, we finished this section!

This is how it looks like, you can write whatever name for the task you want, but...

As you can see, because we already have this task, we can't make another task that's the same!

Oh no! I actually don't want to do this task. How do I get rid of it? Onto the next section!


delete_task() Function

What if we need to delete a task that we deem to useless like homework or studying? We'll need a function to delete a task!

Let's call it delete_task() (because it's an appropriate name!), and we'll also have a print() statement with the same style as the previous functions to show what this is.

But what task to delete? What if the user can't remember what tasks they have or what they're called? That's why we will call the view_tasks() function so the user can see all their tasks:

Now, with an input(), we'll ask the user to enter the name of the task to delete. We will also use some conditionals as well to determine if that task exists or not; we don't want to delete non-existant tasks right?

We'll be using the same expression as above: if something in something:, but again, we'll replace the somethings with acutal variables. All this will also be in a while True: loop to keep on going until the user enters a valid name of task.

Then, we'll delete that task using del; and we'll select the key (the task name) from the dict (todo_name_done) and remove it.

You'll also see some not neccessary parts, like above. It's only to make the program look good. I'll indicate what's neccessary and what isn't:

As you can see, using del, we've got key (remove_task_name) from the input(), and then used it to find it in the dict todo_name_done, and it's removed.

Now remember, we've successfully deleted that task from the dict, but not from the 2 lists completed_tasks and not_completed_tasks! So if we guess, and decide to always remove that task from say, the not_completed_tasks list. What if it's not in that list, meaning that it is completed and is instead in the completed_tasks list? The program will halt, and an error will come:

But we won't know if the task that is being deleted is completed or not. That's why we'll use try and except. What does try and except do? Well, first, you put the code that you want to execute inside the try branch. The program shall then execute that part like normal, however, if it encounters an error, then it will go to the except part. This is useful as it kind of "conceals" errors to the user and keeps the program running.

So we'll have in the try block the code that takes off the deleted task from the not_completed_tasks, then, if the program encounters an error (like if the task is already completed and is in the completed_tasks list, hence not in the previous one), we'll know the task is in the completed_tasks list and remove it from there.

At the end, we'll have break to go back to the main program:

Now, we're almost done with the delete_task function; we just have to add a final else statement (that comes from:
if remove_task_name in todo_name_done: # checks if the task exists) so that the program alerts the user if they selected a task to delete that isn't found in that dict todo_name_done (meaning it doesn't exist).

Nice! We've completed this section, now the user shall be able to remove their tasks! How it's going to look like when select a task to be deleted:

And this confirms we deleted the task!

Wait! What if I've made a mistake, but I don't want to delete the task? How do I edit? Let's continue!


edit_task Function

We'll use this function edit a task to rename it. This section will probably be a bit longer/complicated, but we'll do it!

Starting with the same kind of model/design as the previous functions (and we'll also add view_tasks so user can see their tasks):

Next, we'll have a while True: loop with an input() and conditionals to determine if the task the user typed is a valid one. If yes, we'll use break to get out of that loop. If the user makes a typo or writes a non-existant task name, there shall be an else statement to output an error message and return back to the input():

Then, another while True: loop as well as another input() to ask the user what they want to change their task to. It'll use some concatenation to show the old name of the task:

After, same thing as just before; an if and else statement, but this time to check if the new name of the task is already used. If yes, we'll use pass to loop back for the user to use another name, and if it's valid (meaning the task name is not used)...we'll get to that soon!

Alright, as you can, we have 2 while True loops; just to clarify, the first one is for getting the name of the first task. The second one is for getting the new name of the task.

An uneccessary but still cool small part (outputs momentarily "Editing...", then clears the console, and outputs with concatenation the old task name as well as the new task name).

Now, time for the actual editing part! Since the task name is the key of the dict todo_name_done; reminder, this is how it looks like:

So since the user will be changing the name of the task, the key of the dict, we'll first make a new item with the same value (the empty/full box) but a different key which will be new_task_name. Then, like in the delete_task function, using del, we delete the old item with the old key.

Next, we'll have to update this new name in the completed_tasks and not_completed_tasks list. Since we don't know if the task is completed or not, we'll achieve this with some for loops which will also use enumerate. In the for loop, we'll use conditionals to find the old name of the task, and then replace it with the new name of the task. Therefore, when we find it in one of the lists, say completed_tasks, we will update it there, and since that task doesn't exist in the other list, not_completed_tasks, nothing will be done there.

Finally, we'll use break to get out of the while loop.

So as you can see, we are updating the name of the task both in the dict todo_name_done as well as in the lists completed_tasks and not_completed_tasks. Remember, when we use enumerate, it's to number each "loop" or each time the program goes back to the start of the for loop. n holds the variable holding the current number from enumerate, and i holds the current item in the list.

We use enumerate (which conveniently starts at 0, like lists' indexing) so that with indexing we can find the task and change it to a new name.

Woah, we've just completed the edit_task function! Good job!

This is how it looks like when you edit a task:

And if you select a task that doesn't exist to edit:

And finally if you choose to rename a task to a name already used:

Now we can make new tasks, delete them, and edit them, what else? Well, we have to complete tasks! That's the whole point of this program!


complete_task() Function

This useful function is probably one of the most important functions of the program, because, like I just said, it's the whole point of this Todo list organizer.

As ya'll know, I'll be using the same "style" as the previous functions, also calling the view_task() function so the user can see their tasks:

Now, we're going to have a while True: loop which will contain some conditional branches. The first conditionals will check if the user has any tasks; by using len() we'll check if the todo_name_done dict is empty.

After this, there'll be an input() to get the task that the user wants to complete.

Next, the second part of the conditionals where we check if the task the user wants to complete is a valid one (if it exists in the dict todo_name_done):

That's quite a bunch of code at once, you can read the comments to understand more! As you can see, I've used the no_tasks variable. If you recall, we've defined it waaaay above in the beginning. We shall use it next to determine if we should complete a task or not (because if the dict is empty, there's nothing to complete).

After, we'll notify the user that we're completing the task, pause the program, and clear the console.

Now remember, when the user enters the task, we won't know if it's completed or not. That's why we'll use try and except in case the user wants to complete a task that is already completed. In the try block, we'll first "try" to make the value of the item (the task) a full square ("(▣)") to indicate that it's completed.

Then, we'll remove that task from the not_completed_tasks list (using .remove) and add it to the complete_tasks list (using .append).

Finally, we'll output a confirmation message to the user, pause the program for 2 seconds to let the user read it, and then it'll automatically exit the function.

Here's how it'll look like:

Now, for the last part of our complete_task() function, we'll have the except to join our previous try, and a final else statement (from the if no_tasks == False:).

So like I said above, if the program encounters an error in the try block (if it tries to remove the task from the not_completed_tasks list with not_completed_tasks.remove(task_to_complete)), then it will go skip directly to the except. In there, it'll output an error message and pause the program for 2 seconds to let the user read it, then will exit back to the main menu (we'll cover this soon).

Good job, we completed the complete_task() function. A few pictures:

Confirmation message when we've completed a task:

And if we try to complete a task that is already completed:

One of the final sections of our program, the un_complete_task() function to un-complete tasks! Almost done, let's continue!


un_complete_task() Function

If the user wants to change their mind about a task, or completed one by accident, we'll need a function to un-complete a task. This function will really be almost the same as the complete_task() function so we're just gonna copy paste the previous code, and with the comments I'll indicate what's different/new:

As you can see, many of this code is the same as the complete_task() function. The only things that are different are the print() statements that alert the user (they're different for some 'personality' of the program).

Then, in the try block, most of the code is different; in the todo_name_done dict we replace the full square with the empty square to indicate un-completedness. Then, we remove that task from the completed_tasks list, and append it to the un_completed_tasks list. We then output a message of confirmation, pause the program for 2 seconds, and that's all.

In the except block, the code will run if the program finds an error in the above try, and if so, we'll have a print() statememt to tell the user the task is already un-completed, or not completed.

And that's it for the un_complete_task() function! A few pictures:

Un-completing a task:

And the error message if we un-complete a already un-completed task:

And yay, we're onto the last part of our progam, the main_menu() function! Almost done!


main_menu() Function

This function will hold the code for the main menu where the user can choose to do their actions. We'll first define the function, clear the console so the menu looks clean, and show the user's tasks:

Now, we're going to show the different options the user can do with a print() statement using triple double quotes ("""). Then, we'll have an input() to ask the user what they want to do (with the ¬ to prompt the user), and another os.system('clear') to clear the console.

Next, some conditionals to determine what the user wants to do, and call the appropriate function:

Those if and elif statements check if what the user wrote is equal to one of the numbers; where each of those numbers from 1 to 5 represent a different action.

The last bit of code!!

For the last part of our program, we'll have a while True: loop so this program goes on and on, and we'll call the main_menu() function (which in turn calls the other functions on the user's input):

Woah! That's it!! You've made it Todo List Organizer in Python! You can add a bunch of other cool things to customize your program, like changiing the error message/confirmation message strings, changing the time the program pauses, etc.


Colors (optional)

You can also add colors! I won't exactly be making a tutorial, but here's a quick crash course:

Output:

You can apply them for example in the main menu where we show the different options the user can do, using .format() so it's easier. Here's an example of using .format():

As you can see, in the .format(), we say that gb equals the variable grey_back, which holds the ANSI escape codes for a grey background. It's the same thing for r and reset. The reason you would want to do that is to make your life easier and not have to write the whole grey_back or reset each time.

Here's a bigger example with the main menu:

I'm using blue_back, orange_back, and reset. You can see that there is an empty space on the first line after the print() statement ({r}{ob}); well if you highlight it, you can see that I've pressed space a lot of times so that the orange background creates a kind of wall.

This is the output:

Very pretty! You can use this for many other parts of the program and of course choose your own colors! :D


Closing

Well, that's about it! You now know how to create your own Todo List Organizer program! Hope you've enjoyed, and if you have any questions, comment them down below and I'll do my best to help you! =)

  • Have a super day!

Goodbye, and cya soon! 👋

(Pretty soon because I think I'll be posting a Typing Speed Test repl soon! xD)

PS: Video on how to make your repls more efficient and use less CPU Enjoy! ;)

Original repl with colors and everything: https://repl.it/@Bookie0/Todo-list-Jam

You are viewing a single comment. View All
Bookie0

@LingWu1 hum ok best of luck! :D