How can I add objects based on user input in PyQt5?

I am making a basic contacts app with PyQt5 and SQL. Once the app opened, I want to show all the contacts on the screen. So I want to add a few widgets to the UI for every contact but I don’t know how many of them will be added at start (since the user may change the amount of contacts with adding or deleting a contact) so I can’t add them manually. I tried it with a for loop but since the objects’ names must be diffrent from each other, it didn’t worked.

Here’s the code:

from PyQt5 import QtCore, QtGui, QtWidgets
import sqlite3

class Contact:
    def __init__(self, id, first_name, last_name, phone_numbers, note = ""):
        self.id = id
        self.first_name = first_name
        self.last_name = last_name
        self.phone_numbers = phone_numbers
        self.note = note


    def getBoxInfo(self):
        return f"{self.first_name}, {self.last_name}"


    def getAllInfo(self):
        if self.note != "":
                return f"{self.first_name}, {self.last_name}, {self.phone_numbers}, {self.note}"
        else:
                return f"{self.first_name}, {self.last_name}, {self.phone_numbers},  "


#setting the connection to the database
connection = sqlite3.connect('contacts.db')

cursor = connection.cursor()

#getting 'user' and 'phone_number' datas from the database
cursor.execute("SELECT * FROM users")
connection.commit()

Users = cursor.fetchall()

cursor.execute("SELECT * FROM phone_numbers")
connection.commit()

PhoneNumbers = cursor.fetchall()

#Putting together the 'user' and 'phone_number' datas in the 'Contact' class subject and adding them to an array
Contacts = []
for i in range(len(Users)):
    newContact = Contact(Users[i][0], Users[i][1], Users[i][2], list(), Users[i][3])
    if newContact.note == None:
        newContact.note = ""
    
    for j in PhoneNumbers:
        if j[1] == Users[i][0]:
            newContact.phone_numbers.append(j[2])

        Contacts.append(newContact)

#Creating the UI
class Ui_MainWindow(object):
    def setupUi(self, MainWindow, Contacts):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1200, 675)

        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")

        #The part I tried to create widgets as many as 'users'
        for i in range(len(Contacts)):
            self.widget = QtWidgets.QWidget(self.centralwidget)
            self.widget.setGeometry(QtCore.QRect(30, 30, 241, 81))
            self.widget.setObjectName(f"widget{i}")

            self.pushButton = QtWidgets.QPushButton(self.widget)
            self.pushButton.setGeometry(QtCore.QRect(150, 20, 75, 23))
            self.pushButton.setObjectName(f"pushButton{i}")

        MainWindow.setCentralWidget(self.centralwidget)

        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 816, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)

        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    MainWindow = QtWidgets.QMainWindow()

    ui = Ui_MainWindow()
    ui.setupUi(MainWindow, Contacts)

    MainWindow.show()
    sys.exit(app.exec_())

Answer

The problem has nothing to do with object names, but with the fact that widgets are constantly added at the same fixed coordinates. The result is that you have overlapping widgets, and you can only see the last ones that have been added.

While you could use an increasing y position, that won’t work well: even assuming you’re going to adjust the full height of the main window based on the last added widget position, using fixed geometries is considered a bad practice, for many reasons; in this specific case, you could end up with a window that is too tall, and if you try to resize it some widgets will be hidden.

The solution is to use layout managers, that ensure that all contents are properly shown, and the window will never be smaller than the size required by its children.

For a situation like this, a QVBoxLayout can be a good choice.

Note that editing a pyuic generated file is also considered a bad practice, so I suggest you restart your project with an empty main window in Designer, save that UI, generate the file agan with pyuic and then import that file in your main script. In the following example, I’m assuming that you’ve called that file mainWindow_ui.py:

from PyQt5 import QtWidgets
from mainWindow_ui import Ui_MainWindow

# ...
# contacts loading code...
# ...

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        layout = QtWidgets.QVBoxLayout(self.centralWidget())
        self.contactWidgets = []

        for i, contact in enumerate(Contacts):
            contactWidget = QtWidgets.QWidget()
            layout.addWidget(contactWidget)
            contactLayout = QtWidgets.QHBoxLayout(contactWidget)
            edit = QtWidgets.QLineEdit(contact.first_name)
            pushButton = QtWidgets.QPushButton(f"Edit {i}")
            contactLayout.addWidget(edit)
            contactLayout.addWidget(pushButton)
            self.contactWidgets.append((edit, pushButton))

Now, the only problem here is that if you have a lot of contacts, the window will grow in height. To avoid that, you can use a QScrollArea, but that has to be added to the central widget. You can do that directly in Designer, so drag a QScrollArea to the main window, then right click on an empty space of the window and select “lay out vertically” from the “Lay out” submenu, rebuild the UI with pyuic and just change the layout constructor:

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        layout = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents)
        self.contactWidgets = []

        for i, contact in enumerate(Contacts):
            # same as above
            # ...

        # a "spacer" at the bottom, in case there are not enough contact 
        # widgets to vertically fill the scroll area
        layout.addStretch()

To better understand how UI files can and should be used (remember, pyuic files should never be modified), read more on the official guidelines about using Designer.