Build a Budget App - Build a Budget App

Tell us what’s happening:

My string provided by dunder str method appears to be formatted correct. I checked that each line consists of 22 characters + space + 7.2f for value but I still cannot pass step 16

Your code so far

def __str__(self):
        #star = '*'
        header = f"{'*'.ljust(int((30-len(self.name))/2),'*')}" + self.name + \
                 f"{'*'.ljust(int((30-len(self.name))/2),'*')}\n"
        ledger_list = ''
        desc = ''
        for item in self.ledger:
            if len(item['description']) > 23:
                desc = item['description'][0:22]
            else:
                desc = item['description']
            ledger_list += desc.ljust(22)+' '+"{:7.2f}".format(item['amount'])+"\n"
        last_line = "Total: "+ "{:7.2f}\n".format(self.get_balance())
        return header + ledger_list + last_line

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36

Challenge Information:

Build a Budget App - Build a Budget App

GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-budget-app/5e44413e903586ffb414c94e.md at main · freeCodeCamp/freeCodeCamp · GitHub

Please post all of your code so we can test.


There are two ways you can format your code to make it easier to read and test:

  1. After you copy/paste your code into the editor, select it by dragging your cursor over it then click the (</>) button in the toolbar to automatically wrap your code in backticks. (You can click on the animated demo image below to enlarge it.)

  1. Manually add three backticks on a new line above your code and on a new line after your code. Note that a backtick is NOT the same as a single quote('). To find the backtick key on your keyboard, see this post.

To see changes to your post as you make them, you can click the (M+) button on the toolbar to bring up the rich text editor:


Also, you might want to research how to format using f-strings since you’re using code like '+"{:7.2f}" in normal string concatenation. Aren’t you seeing an error in the console?

No I am not getting any Errors in the console or in PyCharm with that code.

class Category:
    def __init__(self, name):
        self.ledger = []
        self.name = name

    def __str__(self):

        header = f"{'*'.ljust(int((30-len(self.name))/2),'*')}" + self.name + \
                 f"{'*'.ljust(int((30-len(self.name))/2),'*')}\n"
        ledger_list = ''
        for item in self.ledger:
            ledger_list += item['description'].ljust(23)+"{:7.2f}".format(item['amount'])+"\n"
        last_line = "Total: "+ "{:7.2f}\n".format(self.get_balance())
        return header + ledger_list + last_line

    def deposit(self, amount, description=""):
        self.ledger.append({'amount': amount, 'description': description})

    def withdraw(self, amount, description=""):
        if self.check_funds(amount):
            self.ledger.append({'amount': -amount, 'description': description})
            return True
        else:
            return False


    def get_balance(self):
        balance = 0
        for entry in self.ledger:
            balance += entry['amount']
        return balance

    def transfer(self, amount, dest_category):
        if self.withdraw(amount, f'Transfer to {dest_category.name}'):
            dest_category.deposit(amount, f'Transfer from {self.name}')
            return True
        else:
            return False


    def check_funds(self, amount):
        current_balance = self.get_balance()
        if amount > current_balance:
            return False
        else:
            return True

def create_spend_chart(categories):
    header = "Percentage spent by category\n"
    bar_graph_data = {}
    total_withdraw = 0
    for cat in categories:
        cat_total = 0
        for item in cat.ledger:
            if item['amount'] < 0:
                cat_total += item['amount']
        #print(f"For:{cat.name} Total withdrawals: {cat_total}")
        bar_graph_data[cat.name] = {'total': cat_total, 'percentage': 0}
        total_withdraw += cat_total
    keys = bar_graph_data.keys()
    # Update percentages in grap data dictionary
    for key in keys:
        bar_graph_data[key]['percentage'] = (int((bar_graph_data[key]['total']/total_withdraw)*100) // 10) * 10
    # Make a list of lines constituting a bar graph
    # Each line should be 14 characters + new line
    graph_lines = []

    for k in range(100,-10,-10):
        current_line = ''
        current_line += f'{str(k):>3}|'
        for item in bar_graph_data.keys():
            if bar_graph_data[item]['percentage'] < k:
                current_line += "   "
            else:
                current_line += " o "
        graph_lines.append(current_line+"  ")
    # Get names of the categories from the submitted list
    #Check if all lines are 15 characters long
    for item in graph_lines:
        print(len(item))
    cat_names = []
    for cat in categories:
        cat_names.append(cat.name)

    #Find out the maximum length of a category name
    max_name_length = max(map(len,cat_names))

    footer = [" " * 4 + "---" * len(cat_names) + "-" * 2]
    # print(f'Footer is:{footer}')
    # print(f'Footer Length is: {len(footer)}')
    for k in range(0,max_name_length):
        next_line = '    '
        for i in range(0, len(cat_names)):
            try:
                next_char = f' {cat_names[i][k]} '
            except IndexError:
                next_char = '   '
            finally:
                next_line += f'{next_char}'

        footer.append(next_line+"  ")
    # print(f'Length of header is:{len(header)}')
    # print(f'Now footer is:{footer}')
    # print(f'Length of footer is: {len(footer)}')
    # for item in footer:
    #     print(f'{item},Length{len(item)}')
    return header + "\n".join(graph_lines) + "\n" +"\n".join(footer)

food = Category('FOOD')
auto = Category('AUTO')
clothing = Category('CLOTHING')
accommodation = Category('ACCOMMODATION')
accommodation.deposit(1500, 'Initial Deposit')
clothing.deposit(240, 'Initial Deposit')
clothing.withdraw(38, 'T-shirts')
clothing.withdraw(68, 'Jacket')
food.deposit(240, 'Initial Deposit')
auto.deposit(100, 'Initial Deposit')
auto.withdraw(12, 'Breaks job')
food.withdraw(45, 'Sausages and Turkey and some vegies')
food.transfer(67, auto)
auto.withdraw(50, 'New Tires')
print(food)
print(auto)
print(clothing)
print(accommodation)
print(create_spend_chart([auto,food,clothing,accommodation]))

I also noticed that my code does not center the name of the Category correctly if the name has odd number of characters. In fact I would go so far as to say that it is impossible to center an odd character word within a 30 caracter string. Is there a way of doing it?

Please take a look at this reference for f-string formatting suggestions, particularly Example 4:

String Alignment in Python f-string - GeeksforGeeks

Thank you for your reply. I thought that centering means the same number of stars on each side of the name. Introducing the f-string formatting does not change my output but it also does not allow me to get through step 16. Any suggestions much appreciated.
Marek

Please focus on making sure the user stories are implemented correctly.

You have not implemented User Story #4’s second bulleted item:

  • List each ledger entry with up to 23 characters of its description left-aligned and the amount right-aligned (two decimal places, max 7 characters).
*************FOOD*************
Initial Deposit         240.00
Sausages and Turkey and some vegies -45.00
Transfer to AUTO        -67.00
Total:  128.00

Thanks again. I got sort of epiphany after your previous reply and just finished implementing it for other lines of the str method but still cannot get pass step 16.
Here is what I have right now:

class Category:
    def __init__(self, name):
        self.ledger = []
        self.name = name

    def __str__(self):

        header = f"{self.name:*^30}\n"
        ledger_list = ''
        for item in self.ledger:
            chunk = f"{item['description']: <23.23}{item['amount']:>7.2f}"
            ledger_list += chunk+"\n"
        last_line = "Total: {:7.2f}\n".format(self.get_balance())
        return header + ledger_list + last_line

    def deposit(self, amount, description=""):
        self.ledger.append({'amount': amount, 'description': description})

    def withdraw(self, amount, description=""):
        if self.check_funds(amount):
            self.ledger.append({'amount': -amount, 'description': description})
            return True
        else:
            return False


    def get_balance(self):
        balance = 0
        for entry in self.ledger:
            balance += entry['amount']
        return balance

    def transfer(self, amount, dest_category):
        if self.withdraw(amount, f'Transfer to {dest_category.name}'):
            dest_category.deposit(amount, f'Transfer from {self.name}')
            return True
        else:
            return False


    def check_funds(self, amount):
        current_balance = self.get_balance()
        if amount > current_balance:
            return False
        else:
            return True




def create_spend_chart(categories):
    header = ["Percentage spent by category"]
    bar_graph_data = {}
    total_withdraw = 0
    for cat in categories:
        cat_total = 0
        for item in cat.ledger:
            if item['amount'] < 0:
                cat_total += item['amount']
        bar_graph_data[cat.name] = {'total': cat_total, 'percentage': 0}
        total_withdraw += cat_total
    keys = bar_graph_data.keys()

    # Update percentages in grap data dictionary
    for key in keys:
        bar_graph_data[key]['percentage'] = (int((bar_graph_data[key]['total']/total_withdraw)*100) // 10) * 10

    # Make a list of lines constituting a bar graph
    # Each line should be 4 + num of categs*3 + 2
    graph_lines = []

    for k in range(100,-10,-10):
        current_line = ''
        current_line += f'{str(k):>3}|'
        for item in bar_graph_data.keys():
            if bar_graph_data[item]['percentage'] < k:
                current_line += "   "
            else:
                current_line += " o "
        graph_lines.append(current_line+"  ")

    # Find out the maximum length of a category name
    max_name_length = 0
    cat_names = []
    for cat in categories:
        cat_names.append(cat.name)
        if len(cat.name) > max_name_length:
            max_name_length = len(cat.name)
    # print(max_name_length)

    footer = [" " * 4 + "---" * len(categories) + "-" * 2]

    for k in range(0,max_name_length):
        next_line = '    '
        for i in range(0, len(categories)):
            try:
                next_char = f' {cat_names[i][k]} '
            except IndexError:
                next_char = '   '
            finally:
                next_line += f'{next_char}'

        footer.append(next_line+"  ")
    
    all_lines = header+graph_lines+footer
    # for i, line in enumerate(all_lines, start=1):
    #     print(f'{i}. Length: {len(line)}| {line}')
    return "\n".join(all_lines)

food = Category('FOOD')
auto = Category('AUTO')
clothing = Category('CLOTHING')
accommodation = Category('ACCOMMODATION')
accommodation.deposit(1500, 'Initial Deposit')
clothing.deposit(240, 'Initial Deposit')
clothing.withdraw(38, 'T-shirts')
clothing.withdraw(68, 'Jacket')
food.deposit(240, 'Initial Deposit')
auto.deposit(100, 'Initial Deposit')
auto.withdraw(12, 'Breaks job')
food.withdraw(45, 'Sausages and Turkey and some vegies')
food.transfer(67, auto)
auto.withdraw(50, 'New Tires')
print(food)
print(auto)
print(clothing)
print(accommodation)
print(create_spend_chart([auto,food,clothing,accommodation]))

Focus on this bit:

Are you trying to slice?

No. I am left justifying and truncating at the same time. Perhaps the correct way of doing it is

chunk = f"{item['description']: <.23}

But that also does not fix my problem.

Yes, sorry; that’s not the issue, this is:

Here you are using f-string syntax, but is that an f-string?

The original code is not my latest version. I am not sure if it is ok to poste the entire code every time here?
Here is my str() now:

def __str__(self):
    lines = [f"{self.name:*^30}"]

    for item in self.ledger:
        chunk = f"{item['description']:<23.23}{item['amount']: >7.2f}"
        lines.append(chunk)
        lines.append(f"Total: {self.get_balance():<23.2f}")
    # Verify length of lines
    # for i, line in enumerate(lines, start=1):
    #     print(f'{i}. Length: {len(line)}| {line}')
    return "\n".join(lines)+"\n"

I decided to make a list of lines so that I can verify easily that they are of the same length. They are.

This just needs to be left justified, so you can remove 23 from that since it doesn’t apply here.

That should get you to the chart headaches. :rofl:

To get more detail about chart errors, be sure to open your browser’s console after running the tests.

Following is an explanation of how to interpret what you see. Note that these are only examples.


The assertion error and diff gives you a lot of information to track down a problem. For example:

AssertionError: 'Year' != 'Years'
- Year
+ Years
?     +

Your output comes first, and the output that the test expects is second.

AssertionError: ‘Year’ != ‘Years’

Your output: Year does not equal what’s expected: Years

This is called a diff, and it shows you the differences between two files or blocks of code:

- Year
+ Years
?     +

- Dash indicates the incorrect output
+ Plus shows what it should be
? The Question mark line indicates the place of the character that’s different between the two lines. Here a + is placed under the missing s .

Here’s another example:

E       AssertionError: Expected different output when calling "arithmetic_arranger()" with ["3801 - 2", "123 + 49"]
E       assert '  3801      123    \n   - 2     + 49    \n------    -----    \n' == '  3801      123\n-    2    +  49\n------    -----'
E         -   3801      123
E         +   3801      123    
E         ?                ++++
E         - -    2    +  49
E         +    - 2     + 49    
E         - ------    -----
E         + ------    -----    
E         ?                +++++

The first line is long, and it helps to view it as 2 lines in fixed width characters, so you can compare it character by character:

'  3801      123    \n   - 2     + 49    \n------    -----    \n'
'  3801      123\n-    2    +  49\n------    -----'

Again, your output is first and the expected output is second. Here it’s easy to see extra spaces or \n characters.

E         -   3801      123
E         +   3801      123    
E         ?                ++++

Here the ? line indicates 4 extra spaces at the end of a line using four + symbols. Spaces are a little difficult to see this way, so it’s useful to use both formats together.

I hope this helps interpret your error!


I looked at the console output yesterday but got overwhelmed a little. I’ll take another look. Thank you for your help.
Also I used

lines.append(f"Total: {self.get_balance():<23.2f}")

to make sure that the line is 30 character long. I think that f-strings are left justified by default (right?).

But this also does not work for step 16

lines.append(f"Total: {self.get_balance():<.2f}")

Marek

Edit: I got through 16 after removing the “\n” at the very end of str() output. Off to number 20 :wink: