Supporting multi-add/delete (and undo/redo) with a QAbstractItemModel (C++) - c++

Greetings,
I've been writing some nasty code to support the undo/redo of deletion of an arbitrary set of objects from my model. I feel like I'm going about this correctly, as all the other mutators (adding/copy-pasting) are subsets of this functionality.
The code is nastier than it needs to me, mostly because the only way to mutate the model involves calling beginInsertRows/beginRemoveRows and removing the rows in a range (just doing 1 row at a time, no need to optimize "neighbors" into a single call yet)
The problem with beginInsertRows/beginRemoveRows is that removal of a row could affect another QModelIndex (say, one cached in a list). For instance:
ParentObj
->ChildObj1
->ChildObj2
->ChildObj3
Say I select ChildObj1 and ChildObj3 and delete them, if I remove ChildObj1 first I've changed ChildObj3's QModelIndex (row is now different). Similar issues occur if I delete a parent object (but I've fixed this by "pruning" children from the list of objects).
Here are the ways I've thought of working around this interface limitation, but I thought I'd ask for a better one before forging ahead:
Move "backwards", assuming a provided list of QModelIndices is orderered from top to bottom just go from bottom up. This really requires sorting to be reliable, and the sort would probably be something naive and slow (maybe there's a smart way of sorting a collection of QModelIndexes? Or does QItemSelectionModel provide good (ordered) lists?)
Update other QModelIndeces each time an object is removed/added (can't think of a non-naive solution, search the list, get new QModelIndeces where needed)
Since updating the actual data is easy, just update the data and rebuild the model. This seems grotesque, and I can imagine it getting quite slow with large sets of data.
Those are the ideas I've got currently. I'm working on option 1 right now.
Regards,
Dan O

Think of beginRemoveRows/endRemoveRows, etc. as methods to ask the QAbstractItemModel base class to fix up your persistent model indexes for you instead of just a way of updating views, and try not to confuse the QAbstractItemModel base class in its work on those indexes. Check out http://labs.trolltech.com/page/Projects/Itemview/Modeltest to exercise your model and see if you are keeping the QAbstractItemModel base class happy.
Where QPersistentModelIndex does not help is if you want to keep your undo/redo data outside of the model. I built a model that is heavily edited, and I did not want to try keeping everything in the model. I store the undo/redo data on the undo stack. The problem is that if you edit a column, storing the persistent index of that column on the undo stack, and then delete the row holding that column, the column's persistent index becomes invalid.
What I do is keep both a persistent model index, and a "historical" regular QModelIndex. When it's time to undo/redo, I check if the persistent index has become invalid. If it has, I pass the historical QModelIndex to a special method of my model to ask it to recreate the index, based on the row, column, and internalPointer. Since all of my edits are on the undo stack, by the time I've backed up to that column edit on the undo stack, the row is sure to be there in the model. I keep enough state in the internalPointer to recreate the original index.

I would consider using a "all-data" model and a filter proxy model with the data model. Data would only be added to the all-data model, and never removed. That way, you could store your undo/redo information with references to that model. (May I suggest QPersistentModelIndex?). Your data model could also track what should be shown, somehow. The filter model would then only return information for the items that should be shown at a given time.

Related

Undoing cascade deletions in a dense database

I have a fairly large production database system, based on a large hierarchy of nodes each with a 10+ associated models. If someone deletes a node fairly high in the tree, there can be thousands of models deleted and if that deletion was a mistake, restoring them can be very difficult. I'm looking for a way to give me an easy 'undo' option.
I've tried using Django-reversion, but it seems like in order to get the functionality I want (easily reverting a large cascade delete) it needs to store a bunch of information with each revision. When I created initial revisions, the process is less than 10% done and it's already using 8GB in my database, which is not going to work for me.
So, is there a standard solution for this problem? Or a way to customize Django-reversions to fit my use case?
What you're looking for is called a soft delete. Add a column named deleted with a value of false to the table. Now when you want to do a "delete" instead change the column deleted to true. Update all the code not to show the rows marked as deleted (or move the database table and replace it with a view that doesn't show them). Change all the unique constraints to have a filter WHERE deleted = false so you won't have a problem with not being able to add something similar to what user can't see in the system.
As for the cascades you have two options. Either do an ON UPDATE trigger that will update the child rows or add the deleted column to the FK and define it as ON UPDATE CASCADE.
You'll get the whole reverse functionality at a cost of one extra row (and not being able to delete stuff to save space unless you do it manually).

Selecting a random row in Django, quickly

I have a view which returns data associated with a randomly-chosen row from one of my models. I'm aware of order_by('?') and its performance problems, and I want to avoid using order_by('?') in my view.
Because the data in my model changes very rarely (if at all), I'm considering the approach of caching the entire model in memory between requests. I know how many records I'm dealing with, and I'm comfortable taking the memory hit. If the model does change somehow, I could regenerate the cache at that moment.
Is my strategy reasonable? If so, how do I implement it? If not, how can I quickly select a random row from a model that changes very rarely if at all?
If you know the ids of your object, and its range you can randomize over the ids, and then query the database
A better approach might be to keep the number of objects in your cache, and simply retrieve a random one when you need it:
item_number = random.randint(MODEL_COUNT)
MyModel.objects.all()[item_number]

QListWidget performance with many custom items

I have a list with about 2500 custom items. I set them with:
const std::vector<const Items::AbstractItem *> results = _engine.request(text);
if (!results.empty())
{
for (auto i : results){
QListWidgetItem *lwi = new QListWidgetItem;
_results->addItem(lwi);
ListItemWidget *w = new ListItemWidget;
w->setName(i->name());
w->setTooltip(i->path());
_results->setItemWidget(lwi, w);
}
_results->setFixedHeight(std::min(5,_results->count()) * 48); // TODO
_results->show();
}
This takes about 5 seconds on an i5-4590. Hiding the widget is twice as fast. Is this normal or do I have look for errors?
A few ideas:
Try assigning proper parents to your QWidgets, thats way the layout doesn't have to do this
mapping for you. This should help performance.
Call setUpdatesEnable(false) before starting the insert, and to true after it's done
As for hiding the widget while adding large amounts of items, this will help to alleviate extraneous update calls. The second suggestion above should mitigate that.
I think this is fully expected behavior for controls like Lists or Trees that are not based on any data model. And I believe that the data model was invented mainly to fix this issue.
In your situation you have a ListWidget control that stores its data on its own. You need to pass all 2500 items before your app can go on, and you need to do this even if your list shows only 10 items at a time. Even if you just run and close your app, the user won't see all the items but you still need to pass them to your ListWidget. Some GUI frameworks use internal allocation of items and in such case they can optimize things a bit, you could do the same if you allocated your Items in chunks but it's still not a good solution.
Now let's say you introduce some object that could be asked about item properties. The Control will ask about some item and your object will respond with the contents. Your object don't even need to know about all your items, it will just learn when needed.
You Control can ask about few first items and stop when it realize it can fill up its entire height. This way you can avoid work that is not needed for now. The Control can also ask about the item count, so it can set-up its vertical slider.
It needs to be said that the model will not solve your problem automatically, it's just a programming paradigm that allows you to do it better.
So the solution for you would be to replace your QListWidget with a QListView and implement you own data model inheriting QAbstractListModel. You could pass the results to the model and it will pass the items data when needed.
If your QListWidgetItem's always has fixed size, call setUniformItemSizes on your QListWidget, pass true.

Is there a reusable convenience function to update a ManyToManyField's value without clearing it first?

Looking at the Django source, I see that on assignment to a ManyToManyField, all existing values are removed before the new values are added:
https://github.com/django/django/blob/master/django/db/models/fields/related.py#L776
That naive algorithm will introduce tremendous database churn when my application runs—it updates hundreds of thousands of such relationships at a time.
Since, in my case, most of these updates will be noops (i.e., the current and new values will be identical), I could easily reduce churn by updating the M2M field with an algorithm where I first check which objects need to be added, and then check which need to be removed.
This seems like such a common pattern that I'm wondering if a reusable function to do this already exists?

most effective row removal strategy for QStandardItemModel

I have a QStandardItemModel with several 100,000 records of data, and a QSortFilterProxyModel on top of it for filtering and sorting capabilities. I want to remove a substantial number of records, say 100,000, based on the value of one of the columns.
The current implementation iterates over the source model, tests for the value in the appropriate column, and calls removeRow. This turns out to be an extremely slow approach, I don't know why (I've already turned off the signalling of the source model and the sortfilterproxymodel).
What is a more efficient approach?
Can the QSortFilterProxyModel help, e.g. by creating a selection of records to be deleted, and using removeRows?
Thanks,
Andreas
QAbstractItemModel::removeRows() is a candidate, provided that the rows are contiguous. If the model is sorted by the column you are using to do the removal test, then you should be able to use this.
The more effecient approach would be implementing your own model with QAbstractItemModel interface instead of using QStandardItemModel. Then you can build custom indexes which will help you increase performance while deleting items.