Qt: QSqlRelationalTableModel reference to non-existing foreign key - c++

Let's say that I have a table 'person' with the following columns:
id, name, manager_id. Where 'id' is the primary key and 'manager_id' is the foreign key. Since some people might now have a manager this value is allowed to be NULL. However, this seems to create problems with Qt's QSqlRelationalTableModel.
Here is a minimalistic example which replicates the problem:
window.cpp:
Window::Window(QWidget *parent) : QWidget(parent)
{
// setup database
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
db.open();
// create database
QSqlQuery query;
query.exec("create table 'person' (id INTEGER NOT NULL PRIMARY KEY, "
"name varchar(20), manager_id INTEGER NULL)");
query.exec("insert into person values(1, 'Alice', 2)");
query.exec("insert into person values(2, 'Bob', -1)"); // invalid manager_id
//query.exec("insert into person values(2, 'Bob', 1)"); // valid example
// setup model
model = new QSqlRelationalTableModel(this);
model->setTable("person");
model->setEditStrategy(QSqlTableModel::OnRowChange);
// setup foreign key
int typeIndex = model->fieldIndex("manager_id");
model->setRelation(typeIndex, QSqlRelation("person", "id", "name"));
model->select();
// setup UI
auto nameLabel = new QLabel(tr("Name:")); auto nameEdit = new QLineEdit();
auto typeLabel = new QLabel(tr("Manager:")); auto typeComboBox = new QComboBox();
auto nextButton = new QPushButton(tr("Next"));
auto previousButton = new QPushButton(tr("Previous"));
QSqlTableModel *relModel = model->relationModel(typeIndex);
typeComboBox->setModel(relModel);
typeComboBox->setModelColumn(relModel->fieldIndex("name"));
QGridLayout *layout = new QGridLayout();
layout->addWidget(nameLabel, 0, 0, 1, 1);
layout->addWidget(nameEdit, 0, 1, 1, 1);
layout->addWidget(previousButton, 0, 2, 1, 1);
layout->addWidget(nextButton, 1, 2, 1, 1);
layout->addWidget(typeLabel, 2, 0, 1, 1);
layout->addWidget(typeComboBox, 2, 1, 1, 1);
setLayout(layout);
// setup mapper
mapper = new QDataWidgetMapper(this);
mapper->setModel(model);
mapper->setItemDelegate(new QSqlRelationalDelegate(this));
mapper->addMapping(nameEdit, model->fieldIndex("name"));
mapper->addMapping(typeComboBox, typeIndex);
mapper->toFirst();
connect(previousButton, SIGNAL(clicked()), mapper, SLOT(toPrevious()));
connect(nextButton, SIGNAL(clicked()), mapper, SLOT(toNext()));
}
window.h:
#include <QWidget>
class QDataWidgetMapper;
class QSqlRelationalTableModel;
class Window : public QWidget
{
Q_OBJECT
public:
Window(QWidget *parent = 0);
private slots:
private:
QDataWidgetMapper *mapper;
QSqlRelationalTableModel *model;
};
The problem is that the second record (i.e., "Bob") will not be displayed because his manager's id is invalid (-1).
The documentation of the QSqlRelationalTableModel states:
"If a relational table contains keys that refer to non-existent rows in the referenced table, the rows containing the invalid keys will not be exposed through the model. The user or the database is responsible for keeping referential integrity."
However, is there no way around this? It seems to me that this is a common problem. Thanks.

According to your description of the data, "no manager" is an allowable value, so it makes no sense to refer to it as invalid.
The real problem in your example is that the name column has several overlapping puposes. It really should be a separate names table, which can then have a row with an empty string to indicate "no manager".
The person table would then just contain ids from the names table.

Related

QStandardItemModel::findItems does not search in QModelIndex(row, col, QModel(row,col))

I'm trying to create hierarchical tree from sql database.
Tree nodes can have the same name, but unique ID.
Therefore I search for a node parent by its ID, recognize its index and insert a new child row.
Also I know how QModelIndexes are represented in QTreeView.
Example pic
But I'm stuck in QStandardItemModel::findItems. It searches only in QModelIndex(row, col), but never in QModelIndex(row, col, QModel(row,col)).
I would appreciate any advice how to find second column in hierarchical tree model.
Here is my minimum code: main.cpp
#include <QApplication>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QTreeView>
#include <QStandardItemModel>
void createDB()
{
QSqlDatabase m_db = QSqlDatabase::addDatabase("QSQLITE");
m_db.setDatabaseName(":memory:");
if (!m_db.open()) {
return;
}
QSqlQuery query;
query.exec("CREATE TABLE IF NOT EXISTS tags ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"parent_id INTEGER DEFAULT -1 REFERENCES tags(id),"
"title TEXT NOT NULL);");
query.exec("INSERT INTO tags(title) VALUES('item 1');");
query.exec("INSERT INTO tags(title) VALUES('item 2');");
query.exec("INSERT INTO tags(parent_id, title) VALUES(1, 'sub 1');");
query.exec("INSERT INTO tags(parent_id, title) VALUES(2, 'sub 1');");
query.exec("INSERT INTO tags(parent_id, title) VALUES(2, 'sub 2');");
query.exec("INSERT INTO tags(parent_id, title) VALUES(3, 'sub sub 1');");
query.exec("INSERT INTO tags(parent_id, title) VALUES(3, 'sub sub 2');");
}
void createTreeView() {
QTreeView *m_view = new QTreeView;
QStandardItemModel *m_model = new QStandardItemModel;
m_model->setColumnCount(2);
m_view->setModel(m_model);
QSqlQuery query("SELECT id, parent_id, title FROM tags;");
while (query.next()) {
QList<QStandardItem *> node;
node << new QStandardItem(query.value("title").toString());
node << new QStandardItem(query.value("id").toString());
if (query.value("parent_id").toInt() == -1) {
m_model->appendRow(node);
} else {
QList<QStandardItem *> items = m_model->findItems(query.value("parent_id").toString(), Qt::MatchExactly | Qt::MatchRecursive, 1);
for (QStandardItem *item : items) {
QModelIndex index = item->index().siblingAtColumn(0);
item = m_model->itemFromIndex(index);
item->appendRow(node);
}
}
}
m_view->expandAll();
m_view->resizeColumnToContents(0);
//m_view->hideColumn(1);
m_model->setHeaderData(0, Qt::Horizontal, QVariant());
m_view->show();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
createDB();
createTreeView();
return a.exec();
}
What I want to get:
> item 1
> sub 1
sub sub 1 // does not appear, because `findItems` returns an empty list.
sub sub 2 // same
> item 2
sub 1
sub 2
I have Qt 5.12.1, MSVC 2017 32bit.
You didn't use the correct "second" parameter of the function "StandardItemModel::findItems". The default scan depth of the function is 1 layer. So, if you want to seek recusively, you should use the parameter "Qt::MatchRecursive".It like:
modelname.findItems("xxxx", Qt::MatchRecursive);

How to fill database sqlite tablewidget

I am writing "SQLite" database and I create the database table like this:
void MainWindow::createdata()
{
QSqlQuery query;
query.exec("DROP TABLE messages");
query.exec("CREATE TABLE messages("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"IPAddress VARCHAR(20),"
"date VARCHAR(10),"
"message VARCHAR(30))");
query.prepare("INSERT INTO messages(IPAddress, date, message) values(?,?,?)");
query.addBindValue("192.168.1.1");
query.addBindValue("jun 3 2016");
query.addBindValue("hello");
if (query.exec()) {
qDebug() << "ok!";
}
else
{
qDebug() << query.executedQuery();
qDebug() << query.lastError();
}
}
And i create "qtablewidget" like this:
QTableWidget* table = new QTableWidget();
table->setRowCount(10);
table->setColumnCount(4);
table->setWindowTitle("Received Message");
table->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
table->setHorizontalHeaderLabels(QString("ID;HostAddress;Date;Message").split(";"));
table->setStyleSheet("QTableView {selection-background-color: blue;}");
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->setSelectionMode(QAbstractItemView::SingleSelection);
QSqlQuery query("SELECT * FROM messages");
but i don't know how should i fill the "tablewidget" to show with query.
Can anyone please help me?Thanks
You should use QTableView (Model based version of table view) and then using QSqlQueryModel, you can populate your table.
QSqlQueryModel *model = new QSqlQueryModel();
model->setQuery(query);
tableView->setModel(model);
To use QSqlQueryModel with a QTableWidget, you should iterate through QSqlQueryModel row by row and add them to your QTableWidget.
QSqlQueryModel *model = new QSqlQueryModel();
model->setQuery(query);
int i;
QSqlRecord row
for(i = 0, row = model->record(i); !row.isEmpty(); i++, row = model->record(i)){
// Get each field using `value` method of variabale 'row'
// and insert this fields to its corresponding cell in QTableWidget
}
Relevant Question:
Setting the model to a QTableWidget

How to make a column of a table editable?

I'm trying to make a table that will allow a user to enter a mark out of 10 given some criteria. I'm connected to my SQLite database and retrieving a column on the left for the description of the criteria, and a column on the right where I want to be able to enter a grade (an editable column). Here is my code:
QSqlQueryModel *model = new QSqlQueryModel();
QSqlQuery* qry = new QSqlQuery(conn.mydb);
qry->prepare("select I.itemDescription, S.grade from Items I, ItemsToStudent S where I.itemID = S.itemID and I.rubricID = ? and S.courseID = ? and S.studentID = ?");
qry->addBindValue(actid);
qry->addBindValue(courseid);
qry->addBindValue(studentid);
qry->exec();
model->setQuery(*qry);
ui->rubricTable->setModel(model);
However, I can't make the second column editable. How would I go about accomplishing this?
The QSqlQueryModel class provides a read-only data model for SQL result sets. You can use QSqlRelationalTableModel like:
QSqlRelationalTableModel *model = new QSqlRelationalTableModel(this,conn.mydb);
model->setEditStrategy(QSqlTableModel::OnFieldChange);
model->setJoinMode(QSqlRelationalTableModel::LeftJoin);
model->setTable( "Items" );
model->setRelation( 0, QSqlRelation( "ItemsToStudent", "itemID", "grade" ) );
model->setFilter( QString("rubricID = ? and courseID = ? and studentID = ?") );
model->select();
model->setHeaderData( 1, Qt::Horizontal, tr("Criteria") );
model->setHeaderData( 2, Qt::Horizontal, tr("Grade") );
ui->rubricTable->setModel(model);
You can also hide the columns you don't want to show like:
ui->rubricTable_tbl->hideColumn(3);

How to add qdate to qtableview

I want to add Qdate to my table say QTableview.The problem is if i convert it into string i can add and retrieve the data.But i want to store as date only in my model.
void MainWindow::setUpTabel()
{
QDateTime myDate;
myDate.setDate(QDate::currentDate());
//myModel
QStandardItemModel model = new QStandardItemModel(this);
QStandardItem *item = new QStandardItem;
item.setData(myDate,Qt::UserRole);
//Myview is also created and set the model to it
m_tableView->setModel(model);
}
The problem is i'm not able to see the date in my table.
As the documentation says, you must set the item into the model specifying the row and columng where you are going to set the item.
http://qt-project.org/doc/qt-4.8/qstandarditemmodel.html
Modifying your code:
void MainWindow::setUpTabel()
{
int row = 0, column = 0; // here you decide where is the item
QDateTime myDate;
myDate.setDate(QDate::currentDate());
QStandardItemModel model = new QStandardItemModel(this);
QStandardItem *item = new QStandardItem(myDate);
model.setItem(row, column, item);
m_tableView->setModel(model);
}

Retrieving a QStandardItem through QStandardItemModel by searching or key

Is there any way to assign a unique key to an entry in a QStandardItemModel so that we can check for the presence of that key. If it is present we get the relevant QstandardItem ?
Update:
Here is what I am trying to do. I have 3 column in my table so so i have 3 QStandardItem.
This is the code I am using:
QStandardItem* item0 = new QStandardItem("Column1");
QStandardItem* item1 = new QStandardItem("Column2");
QStandardItem* item2 = new QStandardItem("Column3");
Now my model is called model and I am attaching these to my model as such
moddel->setItem(0,0,item0);
moddel->setItem(0,1,item1);
moddel->setItem(0,2,item2);
I need to assign a row some unique key so that I could check the model for that key and the model would return the row number. Any suggestions.
You could use the setData function of QStandardItem in order to set a custom key for a user defined role, eg
#define MyRole Qt::UserRole + 2
myItem->setData(Qvariant(key), MyRole)
You can get the data of any index in your model by using the data call.
QVariant d = mymodel->data(anindex, MyRole)
Writing a function that checks if a key exists should be straight forward.
The answer by pnezis addresses the storing of a key but not the accessing of a QStandardItem from the model. I addressed the storing of data with a QStandardItem by sub classing QStandardItem as I needed to store a lot of complex data.
To obtain the QStandardItem from the model you need to create a QModelIndex instance with the row/column and then call itemFromIndex(index)
on the model.
My example is taken from a selection callback.
QModelIndex& selectedItem = itemsSelected.front();
QStandardItemModel* model = reinterpret_cast<QStandardItemModel*>(tableView->model());
if (nullptr == model)
return;
QStandardItem *item = model->itemFromIndex(selectedItem);
if (nullptr == item)
return ;