Lesson 8 : Migrating¶
Download lesson [here]
This is similar to the previous lesson. Here we also demonstrate migration or “purging” of the document collections.
Purging consist of the following:
- Remove erroneus entries from the collection (documents that don’t specify their class with the “classname” key)
- In each document, remove key/value pairs that do not conform to the schema
- In each document, add key/value pairs that are in the schema but are missing in the document
- Remove erroneous “dangling” foreign key references
The DataModel class has a new method called “purge” that calls collections purge methods.
There are also two distinct data models. First, launch the program with the first one
python3 tutorial_8.py 1
And then with the second one
python3 tutorial_8.py 2
now model 2 reads data written by model 1 and does a purge.
(a side note: this is not yet completely debugged)
import sys
# from PyQt5 import QtWidgets, QtCore, QtGui # Qt5
from PySide2 import QtWidgets, QtCore, QtGui
from cute_mongo_forms.column import LineEditColumn, ListEditColumn, CheckBoxColumn
from cute_mongo_forms.row import ColumnSpec, Row
from cute_mongo_forms.container import List, FormSet, EditFormSet
from cute_mongo_forms.db import SimpleCollection
This class helps us to group lists and forms together
class ListAndForm:
def __init__(self,lis,form,title="",parent=None):
self.title=title
self.lis =lis
self.form =form
self.widget =QtWidgets.QWidget(parent) # create a new widget
self.lay =QtWidgets.QVBoxLayout(self.widget) # attach layout to that widget
self.label =QtWidgets.QLabel(self.title,self.widget)
self.subwidget=QtWidgets.QWidget(self.widget)
self.sublay =QtWidgets.QHBoxLayout(self.subwidget)
self.lay.addWidget(self.label)
self.lay.addWidget(self.subwidget)
self.lis. widget.setParent(self.subwidget) # get widget from List and set its parent to widget
self.form.widget.setParent(self.subwidget)
self.sublay. addWidget(self.lis.widget) # add List's internal widget to widget's layout
self.sublay. addWidget(self.form.widget)
self.lis.widget.currentItemChanged. connect(self.form.chooseForm_slot) # connections between list and the form
self.form.signals.new_record. connect(self.lis.update_slot)
self.form.signals.save_record. connect(self.lis.update_slot)
self.form.signals.delete_record. connect(self.lis.update_slot)
self.delete =self.form.signals.delete_record # shorthand
self.modified =self.form.signals.modified
def update(self):
self.form.updateWidget()
Databases, Row types, containers (FormSets, etc.) are all encapsulated into a single class that describes the data model
class DataModel:
def __init__(self):
self.defineSchema()
self.defineLists()
self.initDB()
def dump(self):
for record in self.food_collection.get():
print(record)
print()
for record in self.collection.get():
print(record)
print()
def defineSchema(self):
"""Define column patterns and databases
"""
class FoodRow(Row):
columns=[
ColumnSpec(LineEditColumn, key_name="name", label_name="Name"),
ColumnSpec(LineEditColumn, key_name="price",label_name="Price"),
ColumnSpec(CheckBoxColumn, key_name="spicy",label_name="Is spicy")
]
self.FoodRow=FoodRow
self.food_collection =SimpleCollection(filename="food_test.db",row_classes=[self.FoodRow])
class PersonRow(Row):
columns=[
ColumnSpec(LineEditColumn, key_name="firstname", label_name="First Name"),
ColumnSpec(LineEditColumn, key_name="surname", label_name="Surname"),
ColumnSpec(LineEditColumn, key_name="address", label_name="Address"),
ColumnSpec(CheckBoxColumn, key_name="married", label_name="Is married")
]
self.PersonRow=PersonRow
class PersonRowExtended(Row):
columns=[
ColumnSpec(LineEditColumn, key_name="firstname", label_name="First Name"),
ColumnSpec(LineEditColumn, key_name="secondname",label_name="Second Name"),
ColumnSpec(LineEditColumn, key_name="surname", label_name="Surname"),
ColumnSpec(LineEditColumn, key_name="address", label_name="Address"),
# in the following, we're referring to self.food_collection and there, to the columns with keys "_id" and "name". The ListEditColumn itself is a list of foreign_keys
ColumnSpec(ListEditColumn, key_name="foods", label_name="Favorite foods", collection=self.food_collection, foreign_label_name="name")
]
self.PersonRowExtended=PersonRowExtended
self.collection =SimpleCollection(filename="simple_test.db",row_classes=[self.PersonRow,self.PersonRowExtended])
def defineLists(self):
class PersonList(List):
def makeLabel(self,entry):
try:
st=entry["firstname"]+" "+entry["surname"]
except KeyError:
st="?"
return st
self.PersonList=PersonList
class FoodList(List):
def makeLabel(self,entry):
try:
st=entry["name"]+" ("+str(entry["price"])+" EUR)"
except KeyError:
st="?"
return st
self.FoodList=FoodList
def initDB(self):
"""Write some entries to databases
"""
self.collection.new(self.PersonRow,{"firstname":"Paavo", "surname":"Vayrynen", "address":"Koukkusaarentie 1", "married":True} )
self.collection.new(self.PersonRow,{"firstname":"Martti", "surname":"Ahtisaari", "address":"Lokkisaarentie 1", "married":True} )
# add some foods
self.food_collection.new(self.FoodRow,{"name":"Hamburger","price":10, "spicy":False})
self.food_collection.new(self.FoodRow,{"name":"Hotdog", "price":50, "spicy":False})
self.food_collection.new(self.FoodRow,{"name":"Freedom Fries", "price":10, "spicy":False})
self.food_collection.new(self.FoodRow,{"name":"Bacalao", "price":100,"spicy":False})
self.food_collection.new(self.FoodRow,{"name":"Piparra", "price":1, "spicy":True})
# get ids of some foods ..
bacalao=list(self.food_collection.get(query={"name":"Bacalao"}))[0]["_id"]
piparra=list(self.food_collection.get(query={"name":"Piparra"}))[0]["_id"]
self.collection.new(self.PersonRowExtended,{"firstname":"Juho", "secondname":"Kustaa","surname":"Paasikivi", "address":"Kontulankaari 1", "foods":[] })
self.collection.new(self.PersonRowExtended,{"firstname":"Esko", "secondname":"Iiro", "surname":"Seppänen", "address":"Mellunraitti 3", "foods":[bacalao, piparra] })
self.food_collection.save()
self.collection.save()
def purge(self):
self.food_collection.purge()
self.collection.purge()
Another data model It extends FoodRow and reduces PersonRow.
class DataModel2(DataModel):
def defineSchema(self):
"""Define column patterns and databases
"""
class FoodRow(Row): # Extend the schema
columns=[
ColumnSpec(LineEditColumn, key_name="name", label_name="Name"),
ColumnSpec(LineEditColumn, key_name="price",label_name="Price"),
ColumnSpec(CheckBoxColumn, key_name="spicy",label_name="Is spicy"),
ColumnSpec(CheckBoxColumn, key_name="healthy",label_name="Is Healty")
]
self.FoodRow=FoodRow
self.food_collection =SimpleCollection(filename="food_test.db",row_classes=[self.FoodRow])
class PersonRow(Row): # Reduce the schema
columns=[
ColumnSpec(LineEditColumn, key_name="firstname", label_name="First Name"),
ColumnSpec(LineEditColumn, key_name="surname", label_name="Surname")
]
self.PersonRow=PersonRow
class PersonRowExtended(Row):
columns=[
ColumnSpec(LineEditColumn, key_name="firstname", label_name="First Name"),
ColumnSpec(LineEditColumn, key_name="secondname",label_name="Second Name"),
ColumnSpec(LineEditColumn, key_name="surname", label_name="Surname"),
ColumnSpec(LineEditColumn, key_name="address", label_name="Address"),
# in the following, we're referring to self.food_collection and there, to the columns with keys "_id" and "name". The ListEditColumn itself is a list of foreign_keys
ColumnSpec(ListEditColumn, key_name="foods", label_name="Favorite foods", collection=self.food_collection, foreign_label_name="name")
]
self.PersonRowExtended=PersonRowExtended
self.collection =SimpleCollection(filename="simple_test.db",row_classes=[self.PersonRow,self.PersonRowExtended])
def initDB(self):
pass
The main Qt program.
class MyGui(QtWidgets.QMainWindow):
def __init__(self,parent=None, data_model_index=1):
super(MyGui, self).__init__()
self.data_model_index=data_model_index
self.initVars()
self.setupUi()
def initVars(self):
# *** Choose here your data model ***
if (self.data_model_index==1):
self.data_model=DataModel()
else:
self.data_model=DataModel2()
def setupUi(self):
# self.setGeometry(QtCore.QRect(100,100,800,800))
self.w=QtWidgets.QWidget(self)
self.setCentralWidget(self.w)
self.lay=QtWidgets.QHBoxLayout(self.w)
self.person_view=ListAndForm(self.data_model.PersonList(collection=self.data_model.collection), EditFormSet(collection=self.data_model.collection), "Persons",self.w)
self.food_view =ListAndForm(self.data_model.FoodList (collection=self.data_model.food_collection),EditFormSet(collection=self.data_model.food_collection),"Foods", self.w)
self.food_view.modified.connect(self.person_view.update)
# instantiate PersonFormSet => instantiate rows => row instantiates widgets based on the columns => ..
# updating: call row's updateWidget method => re-creates column widgets
self.lay.addWidget(self.person_view.widget)
self.lay.addWidget(self.food_view.widget)
# self.data_model.dump()
self.data_model.purge()
Start the Qt program
if (__name__=="__main__"):
app=QtWidgets.QApplication([])
gui=MyGui(data_model_index=int(sys.argv[1]))
gui.show()
app.exec_()