Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help Debug please
#1
Bug 
Hello,

I am trying to make a Python program that takes user input and generates labels as a pdf based on what was put in. The proof of concept is done however I am struggling to debug the more intensive version.

Currently the program will not generate the correct amount of labels for one distinct item, and if there are multiple distinct items then the whole thing gets confused. I have a while loop in there currently as you can see and the alternative for loop which was being used is commented. I understand the thing looks messy currently however I must emphasise this is a prototype!

Lastly if someone can help me with the label formatting on the pdf that would be smashing. I need 4 labels to a page, such as:

|1 | 2|
|3 | 4|

whilst i'm currently getting
|1 |
|2 |

*new page*
|1 |
|2 |

and so on.


CODE****************************

#Prepare the global variables and import the PDF maker module.

from fpdf import FPDF
#label list is the list storing the details of each distinct item. This way we have no upper limit to the number of distinct items we can process.
label_list = []
order = "0"
send_line1 = "0"
send_line2 = "0"
send_city = "0"
send_postcode = "0"
rec_line1 = "0"
rec_line2 = "0"
rec_city = "0"
rec_postcode = "0"
#Total labels is the total number of labels we need for each distinct item. The first value (0) would be the number of labels required for all of the first (0) distinct item, the second value (1) for the next distinct item and so on.
total_labels = []
#x acts as a placemark later for knowing how many times we have asked the user for the distinct data.
x = int(1)
#y acts as a placemark to choose an item in the labels list. for example we are looking for items 2 and 3 (total labels per item and total number of items) so we can calculate the total number of labels for that distinct batch.
y = int(2)
#i will act as the number of distinct items later so we know how often to ask for unique data.
i = int(0)
########################################################################
#Intro Message to ensure use of the correct company's program.
def start():
    print ("********************************************************")
    print ("   Welcome to the Prototype Label Template Program V2.0!")
    print ("********************************************************")
    print ("")
    print ("")
    print ("")

    
    sending_address()
########################################################################
#Sending address information.
def sending_address():

    #Accessing variables outside of the function.
    global send_line1
    global send_line2
    global send_city
    global send_postcode

    #Here we are saving the address data which is the same for every item on this order.
    print ("Please input the sending address following the prompts below.")
    print ("Line 1: ")
    send_line1 = input()
    print ("Line 2: ")
    send_line2 = input()
    print ("City/Town: ")
    send_city = input()
    print ("Postcode: ")
    send_postcode = input()

    #Here we are checking if the information is accurate.
    print (send_line1 + ", " + send_line2 + ", " + send_city + ", " + send_postcode)
    print (" ")
    print ("Is this correct? Y/N: ")
    a = input()

    #Possibly add an if error statement so the user doesn't accidentally continue.
    if a == ("N") or a ==("n"):
        sending_address()
    if a == ("No") or a == ("no"):
        sending_address()

        
    recieving_address()
########################################################################
#Recieving address information
def recieving_address():
    global rec_line1
    global rec_line2
    global rec_city
    global rec_postcode

    print ("Please input the recieving address following the prompts below.")
    print ("Line 1: ")
    rec_line1 = input()
    print ("Line 2: ")
    rec_line2 = input()
    print ("City/Town: ")
    rec_city = input()
    print ("Postcode: ")
    rec_postcode = input()
    #See above for relevant comments.
    print (rec_line1 + ", " + rec_line2 + ", " + rec_city + ", " + rec_postcode)
    print (" ")
    print ("Is this correct? Y/N: ")
    b = input ()
    if b == ("N") or b == ("n"):
        recieving_address()
    if b == ("No") or  b == ("no"):
        recieving_address()
    
    order_input()
########################################################################
#Order details such as the unique order number and the number of distinct items.
def order_input():
    #Here we store the order number which will be shown on each label.
    print ("Please input the order number for this order: ")
    global order
    order = input()
    #This part is important. It saves the number of distinct items on the order so the program knows how many times it needs to take in data.
    print ("Please input the number of distinct items on the PO: ")
    global i
    i = int(input())
    
    item_input()
########################################################################
#This will run for every distinct item on the list. It asks for the item details and the quantity.
def item_input():
    
    global x
    global i
    global y
    
    #The first two questions are for information which will show on the label.
    print ("Please input the style number of this item: ")
    label_list.append(input())
    
    print("Please input the colour of this item: ")
    label_list.append(input())

    #Here it will calculate how many labels are required based off of per-unit * number_of_units
    print("Please input the number of labels required for each unit of this item: ")
    label_list.append(int(input()))

    print("Please input the quantity of this item: ")
    label_list.append(int(input()))
    total_labels.append(label_list[y] * label_list[y + 1])
    print (label_list)
    print (total_labels)
    #Here we are checking if the above questions have been asked for each distinct item.
    #Maybe change the below to a for loop.
    if x < i : 
        x = x + 1
        y = y + 4
        item_input()
    label_maker()
########################################################################

#This is the FPDF code which will make the PDF file. 
def label_maker():
    global total_labels
    global label_list
    global order
    global i
    #In theory, threeandfour should act as a reference point to items three and four on the labels_list list which are required in the label.
    threeandfour = int(2)
    #Labels complete is a reference to know how many times it has run through this code.
    labels_complete = int(0)
    style_colour = int(0)
    #z refers to an item on the list. if there are multiple different products, the code will check the first item as z is 0 and once the labels are completed for the first item,
    # it will increment and create all the labels for the next item and so on.
    z = int(0)
    #This saves the FPDF functions.
    pdf = FPDF()
    #This creates a new page. I will need to use this after every four labels.
    pdf.add_page()
    #This code just chooses the font type and size.
    pdf.set_font("Arial", size = 14)


    #for loop in range(total_labels[z]):

    while total_labels[z] != labels_complete:
    #The codeblock below creates one label based on the information provided.
        pdf.cell(100, 10, txt = send_line1,
            ln = 1, align = '')
        pdf.cell(100, 10, txt = send_line2,
            ln = 2, align = '')
        pdf.cell(100, 10, txt = send_city + ", " + send_postcode,
            ln = 3, align = '')
        pdf.cell(100, 10, txt = " ",
            ln = 4, align = '')
        pdf.cell(100, 10, txt = " ",
            ln = 5, align = '')
        pdf.cell(100, 10, txt = "PO#: " + order,
            ln = 6, align = '')
        pdf.cell(100, 10, txt = " ",
            ln = 7, align = '')
        pdf.cell(100, 10, txt = str(label_list[style_colour]) + " " + str(label_list[style_colour + 1]), #Note: Check here later as list item number (0 and 1) will need changing to variable.
            ln = 8, align = '')
        pdf.cell(100, 10, txt = " ",
                ln = 9, align = '')
        pdf.cell(100, 10, txt = " ",
            ln = 10, align = '')
        pdf.cell(100, 10, txt = rec_line1,
            ln = 11, align = '')
        pdf.cell(100, 10, txt = rec_line2,
            ln = 12, align = '')
        pdf.cell(100, 10, txt = rec_city + ", " + rec_postcode,
            ln = 13, align = '')
        pdf.cell(100,10, txt = " ",
                 ln = 14, align = '')
        labels_complete = labels_complete + 1

    #This code checks if the number of completed labels matches the number of labels required for one distinct item. If not, it creates another label as seen below.
        if labels_complete == total_labels[z]:            
            if len(total_labels) > (z + 1):
                style_colour = style_colour + 4
                z = z + 1
                labels_complete = 0
                pdf.add_page()
            
        if total_labels[z] > labels_complete:
            pdf.cell(100, 10, txt = send_line1,
                ln = 1, align = '')
            pdf.cell(100, 10, txt = send_line2,
                ln = 2, align = '')
            pdf.cell(100, 10, txt = send_city + ", " + send_postcode,
                ln = 3, align = '')
            pdf.cell(100, 10, txt = " ",
                ln = 4, align = '')
            pdf.cell(100, 10, txt = " ",
                ln = 5, align = '')
            pdf.cell(100, 10, txt = "PO#: " + order,
                ln = 6, align = '')
            pdf.cell(100, 10, txt = " ",
                ln = 7, align = '')
            pdf.cell(100, 10, txt = str(label_list[style_colour]) + " " + str(label_list[style_colour + 1]),
                ln = 8, align = '')
            pdf.cell(100, 10, txt = " ",
                ln = 9, align = '')
            pdf.cell(100, 10, txt = " ",
                ln = 10, align = '')
            pdf.cell(100, 10, txt = rec_line1,
                ln = 11, align = '')
            pdf.cell(100, 10, txt = rec_line2,
                ln = 12, align = '')
            pdf.cell(100, 10, txt = rec_city + ", " + rec_postcode,
                ln = 13, align = '')
            labels_complete = labels_complete + 1
            

        #this does the same as the above however if there are more distinct items it needs to start making labels for the next item.
        if labels_complete == total_labels[z]:            
            if len(total_labels) < (z + 1):
                style_colour = style_colour + 4
                z = z + 1                
                labels_complete = 0
                pdf.add_page()
            # Note: Check this as it will not currently work. Needs to - If more labels required, go back to the start.
            #if there are no more items in the list it needs to check the length of the list and then say if list length is greater than the item on the list which has just been completed
            #(3 items on a list, item 1 complete) then re run the code.
       
    #if number of distinct items completed is equal to total distinct items, save the labels as a pdf with the order number as the name.
        if z == i:
            break
    pdf.output(order + ".pdf")
########################################################################
#Callback to the start function.
start()
Larz60+ write Jul-26-2021, 10:35 AM:
Please post all code, output and errors (it it's entirety) between their respective tags. Refer to BBCode help topic on how to post. Use the "Preview Post" button to make sure the code is presented as you expect before hitting the "Post Reply/Thread" button.

Fixed for you this time. Please use bbcode tags on future posts.
while dad_has_cigs == True:
    happiness = True
    if dad_has_cigs == False:
    print("Dad come home!")
    happiness = not happiness
    break
Reply
#2
Please wrap code in Python tags so the indentation is retained.

I think you want to do something like this:
from fpdf import FPDF

def print_label(pdf, po, send_address, recv_address, item):
    '''Add label to pdf'''
    pdf.cell(100, 10, txt = send_address[0], ln = 1, align = '')
    pdf.cell(100, 10, txt = send_address[1], ln = 2, align = '')
    pdf.cell(100, 10, txt = send_address[2], ln = 3, align = '')
    pdf.cell(100, 10, txt = " ", ln = 4, align = '')
    pdf.cell(100, 10, txt = " ", ln = 5, align = '')
    pdf.cell(100, 10, txt = "PO#: " + po, ln = 6, align = '')
    pdf.cell(100, 10, txt = " ", ln = 7, align = '')
    pdf.cell(100, 10, txt = item[0] + " " + item[1],  ln = 8, align = '')
    pdf.cell(100, 10, txt = " ", ln = 9, align = '')
    pdf.cell(100, 10, txt = " ", ln = 10, align = '')
    pdf.cell(100, 10, txt = recv_address[0], ln = 11, align = '')
    pdf.cell(100, 10, txt = recv_address[1], ln = 12, align = '')
    pdf.cell(100, 10, txt = recv_address[2], ln = 13, align = '')
    pdf.cell(100, 10, txt = " ", ln = 14, align = '')

def enter_address(prompt):
    '''Enter an address.  Returns ("line1", "line2", "city, postal code")'''
    while True:
        print (prompt)
        line1 = input("Line 1: ")
        line2 = input("Line 2: ")
        city = input("City: ")
        postcode = input("Postcode: ")
        print (f'{line1}\n{line2}\n{city}, {postcode}\n')
        if input("Is this correct? Y/N: ").upper() not in 'No':
            return line1, line2, f'{city}, {postcode}'

def enter_item():
    '''Enter item info for an order.  Returns (style, color, label_count, quantity)'''
    while True:
        style = input("Please input the style number of this item: ")
        color = input("Please input the colour of this item: ")
        label_count = int(input("Please input the number of labels required for each unit of this item: "))
        quantity = int(input("Please input the quantity of this item: "))
        if label_count > 0 and quantity > 0:
            return style, color, label_count, quantity

def enter_order():
    '''Print labels for an order'''
    po = input("Please input the order number for this order: ")
    send_address = enter_address('Enter sending address ')
    recv_address = enter_address('Enter receiving address ')

    order = []
    while True:
        order.append(enter_item())
        if input('Is order complete (y/n)? ').upper in ('Yes'):
            break

    pdf = FPDF()
    pdf.set_font("Arial", size = 14)
    label_count = 0
    for item in order:
        for _ in range(item[2] * item[3]):
            if label_count % 4:
                pdf.add_page()  # 4 labels per page
            print_label(pdf, po, send_address, recv_address, item)
            label_count += 1
    pdf.output(po + ".pdf")

while True:
    enter_order()
    if input('Place another order (y/n)? ').upper() in 'N':
        break
I would also consider using a named tuple or dataclass for the item information. item.quantity is more intuitive than item[3]. Maybe do the same with address, but that is less an issue since we don't really care what any of the address lines contain.
Reply
#3
Bug 
(Jul-26-2021, 12:18 PM)deanhystad Wrote: Please wrap code in Python tags so the indentation is retained.

I think you want to do something like this:
from fpdf import FPDF

def print_label(pdf, po, send_address, recv_address, item):
    '''Add label to pdf'''
    pdf.cell(100, 10, txt = send_address[0], ln = 1, align = '')
    pdf.cell(100, 10, txt = send_address[1], ln = 2, align = '')
    pdf.cell(100, 10, txt = send_address[2], ln = 3, align = '')
    pdf.cell(100, 10, txt = " ", ln = 4, align = '')
    pdf.cell(100, 10, txt = " ", ln = 5, align = '')
    pdf.cell(100, 10, txt = "PO#: " + po, ln = 6, align = '')
    pdf.cell(100, 10, txt = " ", ln = 7, align = '')
    pdf.cell(100, 10, txt = item[0] + " " + item[1],  ln = 8, align = '')
    pdf.cell(100, 10, txt = " ", ln = 9, align = '')
    pdf.cell(100, 10, txt = " ", ln = 10, align = '')
    pdf.cell(100, 10, txt = recv_address[0], ln = 11, align = '')
    pdf.cell(100, 10, txt = recv_address[1], ln = 12, align = '')
    pdf.cell(100, 10, txt = recv_address[2], ln = 13, align = '')
    pdf.cell(100, 10, txt = " ", ln = 14, align = '')

def enter_address(prompt):
    '''Enter an address.  Returns ("line1", "line2", "city, postal code")'''
    while True:
        print (prompt)
        line1 = input("Line 1: ")
        line2 = input("Line 2: ")
        city = input("City: ")
        postcode = input("Postcode: ")
        print (f'{line1}\n{line2}\n{city}, {postcode}\n')
        if input("Is this correct? Y/N: ").upper() not in 'No':
            return line1, line2, f'{city}, {postcode}'

def enter_item():
    '''Enter item info for an order.  Returns (style, color, label_count, quantity)'''
    while True:
        style = input("Please input the style number of this item: ")
        color = input("Please input the colour of this item: ")
        label_count = int(input("Please input the number of labels required for each unit of this item: "))
        quantity = int(input("Please input the quantity of this item: "))
        if label_count > 0 and quantity > 0:
            return style, color, label_count, quantity

def enter_order():
    '''Print labels for an order'''
    po = input("Please input the order number for this order: ")
    send_address = enter_address('Enter sending address ')
    recv_address = enter_address('Enter receiving address ')

    order = []
    while True:
        order.append(enter_item())
        if input('Is order complete (y/n)? ').upper in ('Yes'):
            break

    pdf = FPDF()
    pdf.set_font("Arial", size = 14)
    label_count = 0
    for item in order:
        for _ in range(item[2] * item[3]):
            if label_count % 4:
                pdf.add_page()  # 4 labels per page
            print_label(pdf, po, send_address, recv_address, item)
            label_count += 1
    pdf.output(po + ".pdf")

while True:
    enter_order()
    if input('Place another order (y/n)? ').upper() in 'N':
        break
I would also consider using a named tuple or dataclass for the item information. item.quantity is more intuitive than item[3]. Maybe do the same with address, but that is less an issue since we don't really care what any of the address lines contain.


Hello,

Classes is a good idea as I could link the data up, but that would just help with readability and would limit the number of items you could do in a single session. My main problem is getting the correct quantity of labels for an unlimited number of possible distinct items. This would need to generate anywhere between 500 labels for one item, to 100 labels for 100 items.

This is why my main focus is on the for/while loop idea in the prototype code, however for some reason it will just do a single label and not the following, or it will do the wrong number of labels for all of them.

Cheers,
Jamie
while dad_has_cigs == True:
    happiness = True
    if dad_has_cigs == False:
    print("Dad come home!")
    happiness = not happiness
    break
Reply
#4
Your main problem is that your code is a mess. You use global variables for passing information in and out of functions and you use recursion when you want to repeat code. That is always going to make bad code no matter what the underlying algorithm.

Your first step should be to eliminate all recursive function calls. Recursion has it's uses, but none of them are what you are using recursion for. If you want to input multiple items, use a loop. If you want to ask for information until the information is correct, use a loop. If you ever want to use recursion, use a loop. Maybe that last bit of advice is too harsh, but barely.

Your second step should be to eliminate all the global variables. Global variables make it too hard to write correct code. The main benefit of functions is they break code into little pieces. If each piece works, and you use the pieces correctly the program works (usually). If your functions use global variables, the pieces lose there autonomy. A change to piece "A" might break the code in piece "B", and the only way to spot the error is to look at the code in its entirety which defeats the purpose of using functions in the first place. If a function needs a value, pass in the value as a function argument. If the function generates information, pass the information back as a return.

Look at any repeated code. Why do you have a function to get the sending address and another function to get the receiving address? They both do the same thing. Modify the code so it can do both roles. You also have some duplication in the PDF writer that I don't understand at all. Why is it there?

Once your code is cleaned up a bit I think you will find your error is in how you build the label_list. When you input the items you treat label_list as a flat list that looks like this:
Output:
[item1_style, item1_color, item1_labels, item1_quantity, ... itemn_style, itemn_color, itemn_labels, itemn_quantity]
But when you print the labels you treat the label list like it only contains one item.
       pdf.cell(100, 10, txt = str(label_list[0]) + " " + str(label_list[1]), ln = 8, align = '')
The code would be simpler if the label list was a list of labels/items. label_list[0] should have all the information you need to print labels for that item (size, color, label count, item count) and label_list[1] should be the information for the next item. I would also dump the total_labels list. If you can calculate the number of labels from the item information, do the calculation when needed instead of creating another list that has to be synchronized with another list.
Reply
#5
(Jul-26-2021, 02:34 PM)deanhystad Wrote: Your main problem is that your code is a mess. You use global variables for passing information in and out of functions and you use recursion when you want to repeat code. That is always going to make bad code no matter what the underlying algorithm.

Your first step should be to eliminate all recursive function calls. Recursion has it's uses, but none of them are what you are using recursion for. If you want to input multiple items, use a loop. If you want to ask for information until the information is correct, use a loop. If you ever want to use recursion, use a loop. Maybe that last bit of advice is too harsh, but barely.

Your second step should be to eliminate all the global variables. Global variables make it too hard to write correct code. The main benefit of functions is they break code into little pieces. If each piece works, and you use the pieces correctly the program works (usually). If your functions use global variables, the pieces lose there autonomy. A change to piece "A" might break the code in piece "B", and the only way to spot the error is to look at the code in its entirety which defeats the purpose of using functions in the first place. If a function needs a value, pass in the value as a function argument. If the function generates information, pass the information back as a return.

Look at any repeated code. Why do you have a function to get the sending address and another function to get the receiving address? They both do the same thing. Modify the code so it can do both roles. You also have some duplication in the PDF writer that I don't understand at all. Why is it there?

Once your code is cleaned up a bit I think you will find your error is in how you build the label_list. When you input the items you treat label_list as a flat list that looks like this:
Output:
[item1_style, item1_color, item1_labels, item1_quantity, ... itemn_style, itemn_color, itemn_labels, itemn_quantity]
But when you print the labels you treat the label list like it only contains one item.
       pdf.cell(100, 10, txt = str(label_list[0]) + " " + str(label_list[1]), ln = 8, align = '')
The code would be simpler if the label list was a list of labels/items. label_list[0] should have all the information you need to print labels for that item (size, color, label count, item count) and label_list[1] should be the information for the next item. I would also dump the total_labels list. If you can calculate the number of labels from the item information, do the calculation when needed instead of creating another list that has to be synchronized with another list.

Hello,

The global variables could be downsized, I completely agree and I normally avoid using them. When the concept is working then I will tidy it up etc... I appreciate that currently it is a mess.

The duplication in the pdf code was only recently done. I'm new to python and to FPDF and I was originally trying to make the labels format as shown in my original post. upon failing this I left it as just the basic label format so I could change it later.

Thank you for your input though, I'll give it a clean up and see what works.

Cheers,
Jamie
while dad_has_cigs == True:
    happiness = True
    if dad_has_cigs == False:
    print("Dad come home!")
    happiness = not happiness
    break
Reply
#6
You could try reportlab.

I find pdfs are complex, but I can usually get reportlab to do what I want. I never wanted to make labels, yet.

Download the reportlab user guide as pdf. Especially Chapter 5 may help you.

Chapter 5 PLATYPUS - Page Layout and Typography Using Scripts

5.5 Frames: looks a lot like your labels.

Quote:5.5 Frames
Frames are active containers which are themselves contained in PageTemplates. Frames have a
location and size and maintain a concept of remaining drawable space. The command
Frame(x1, y1, width,height, leftPadding=6, bottomPadding=6, rightPadding=6, topPadding=6, id=None, showBoundary=0)

Also, there is a lot of good info about reportlab online.
Reply
#7
Hello mate,

The Reportlab stuff looks good however I'm not looking to pay for anything. This is a side project for me to learn rather than creating something which will actually be implemented.

You're right about the PDF being complex, however in the current scenario I would need it to be done as a PDF.

Cheers,
Jamie
while dad_has_cigs == True:
    happiness = True
    if dad_has_cigs == False:
    print("Dad come home!")
    happiness = not happiness
    break
Reply
#8
If I were to narrow down my primary issue it is here.

If we assume that test is a list with the values ('1','2','3','4'), I want it to print the 1 twice and print the 3 four times. x and y are both integers with the value 0.

However it is only printing both 1 and 3 twice each.

If we assume the values of test are ('1','5','2','3') then the code will print '1' four times and stop.

    for run in test:
        
        if x < test[y + 1]:
            print ("Item " + str(test[y]))
            x = x + 1
        if x == test[y + 1]:
            print("Next Item")
            y = y + 2
            x = int(0)
        if y == len(test):
            break
while dad_has_cigs == True:
    happiness = True
    if dad_has_cigs == False:
    print("Dad come home!")
    happiness = not happiness
    break
Reply
#9
Hello All,

I found a good solution to the first problem, feeling stupid as I didn't try it before but it is basically:
    ################################################
    while x > -1:
        
        if x < test[y + 1]:
            print ("Item " + str(test[y]))
            x = x + 1
        if x == test[y + 1]:
            print("Next Item")
            y = y + 2
            x = int(0)
        if y == len(test):
            break
    ################################################
Output:
Input test data: 1 How many times? 5 Input test data: 2 How many times? 3 ['1', 5, '2', 3] Item 1 Item 1 Item 1 Item 1 Item 1 Next Item Item 2 Item 2 Item 2 Next Item
while dad_has_cigs == True:
    happiness = True
    if dad_has_cigs == False:
    print("Dad come home!")
    happiness = not happiness
    break
Reply
#10
reportlab is free, as far as I know.

The webpage mouse vs python has some good help on reportlab. But also lots of other sites.

I use it to generate multiple-choice answer forms. Works great!!

In Python just

import reportlab

It has so many bits, you really need to read the user guide very carefully and consult the web.

I can really recommend it!!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  pycharm debug help mg24 1 1,025 Nov-18-2022, 05:38 AM
Last Post: deanhystad
  Python debug suddenly got bad ben1122 3 1,085 Sep-03-2022, 06:20 AM
Last Post: ben1122
  Error in Int object is not subscript-able. How to debug this ? yanDvator 1 2,220 Aug-03-2020, 02:28 PM
Last Post: Larz60+
  is there a debug mode available while creating python egg BhushanPathak 1 2,368 Dec-19-2018, 04:15 PM
Last Post: Larz60+
  Help debug my fibonacci implementation dineshpabbi10 6 3,973 May-16-2018, 12:12 PM
Last Post: dineshpabbi10
  Not sure how to debug this? rsmldmv 3 5,438 Nov-09-2017, 02:51 AM
Last Post: snippsat
  Debug and trace in python quocchi22101262 0 3,486 Jun-20-2017, 09:35 AM
Last Post: quocchi22101262

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020