[12920] | 1 | .. _removecertificatecourses: |
---|
[4488] | 2 | |
---|
[12920] | 3 | Persistence of Certificate Courses |
---|
| 4 | ================================== |
---|
[4488] | 5 | |
---|
[12920] | 6 | If a certificate course requires a certain course and this is course |
---|
| 7 | is deleted, also the referring certificate course is deleted. |
---|
| 8 | |
---|
| 9 | We setup a data structure that reflects typical usage. It looks like |
---|
| 10 | this:: |
---|
| 11 | |
---|
| 12 | Department-Instance |
---|
| 13 | | |
---|
| 14 | +---> courses |
---|
| 15 | | | |
---|
| 16 | | +--------------------> Course-Instance |
---|
| 17 | | ^ |
---|
| 18 | +---> certificates | |
---|
| 19 | | | |
---|
| 20 | +-----> Certificate-Instance | |
---|
| 21 | | | |
---|
| 22 | +------> Certificate-Course |
---|
| 23 | |
---|
| 24 | The Certifcate Course here refers to a Course instance. |
---|
| 25 | |
---|
| 26 | In Python we build such a structure like this (from top to bottom): |
---|
| 27 | |
---|
| 28 | >>> from zope.component import createObject |
---|
| 29 | >>> mydept = createObject('waeup.Department') |
---|
| 30 | |
---|
| 31 | In real world use this data will be stored in a ZODB. We setup our own |
---|
| 32 | litte ZODB backend (which is easy!): |
---|
| 33 | |
---|
| 34 | >>> from ZODB import FileStorage, DB |
---|
| 35 | >>> dbpath = 'tinyData.fs' |
---|
| 36 | >>> class TinyZODB(object): |
---|
| 37 | ... def __init__(self, path=dbpath): |
---|
| 38 | ... self.storage = FileStorage.FileStorage(path) |
---|
| 39 | ... self.db = DB(self.storage) |
---|
| 40 | ... self.connection = self.db.open() |
---|
| 41 | ... self.dbroot = self.connection.root() |
---|
| 42 | ... def close(self): |
---|
| 43 | ... self.connection.close() |
---|
| 44 | ... self.db.close() |
---|
| 45 | ... self.storage.close() |
---|
| 46 | |
---|
| 47 | Now we can use this ZODB as backend database and store our data |
---|
| 48 | structure: |
---|
| 49 | |
---|
| 50 | >>> import transaction |
---|
| 51 | >>> db = TinyZODB() |
---|
| 52 | >>> dbroot = db.dbroot |
---|
| 53 | >>> dbroot['mydept'] = mydept |
---|
| 54 | >>> mycourse = createObject('waeup.Course') |
---|
| 55 | >>> mycourse.code = 'MYCOURSE' |
---|
| 56 | >>> mydept.courses.addCourse(mycourse) |
---|
| 57 | >>> mycert = createObject('waeup.Certificate') |
---|
| 58 | >>> mycert.code = 'MYCERT' |
---|
| 59 | >>> mydept.certificates.addCertificate(mycert) |
---|
| 60 | >>> mycert.addCertCourse(mycourse) |
---|
| 61 | |
---|
| 62 | >>> transaction.commit() |
---|
| 63 | |
---|
| 64 | The data is now stored in the ZODB. We can close the DB, reopen it |
---|
| 65 | later and the data will still be there: |
---|
| 66 | |
---|
| 67 | >>> db.close() |
---|
| 68 | >>> newdb = TinyZODB() |
---|
| 69 | >>> newdbroot = newdb.dbroot |
---|
| 70 | >>> list(newdbroot) |
---|
| 71 | ['mydept'] |
---|
| 72 | |
---|
| 73 | The certificate course we stored in the certificate is indeed a |
---|
| 74 | referrer of the course, not a copy of it: |
---|
| 75 | |
---|
| 76 | >>> course = newdbroot['mydept'].courses['MYCOURSE'] |
---|
| 77 | >>> certcourse = newdbroot['mydept'].certificates['MYCERT']['MYCOURSE_100'] |
---|
| 78 | >>> certcourse.course is course |
---|
| 79 | True |
---|
| 80 | |
---|
| 81 | So, we can be sure that modifications to the course are immediately |
---|
| 82 | reflected in the certcourse. |
---|
| 83 | |
---|
| 84 | We can also be sure, that when removing courses or referrers of them, |
---|
[6737] | 85 | other data will be handled in a way we expect. That is, |
---|
[4488] | 86 | |
---|
[5978] | 87 | * when we remove a course, we want all referrers to it be removed as |
---|
[4488] | 88 | well |
---|
| 89 | |
---|
[5978] | 90 | * when we remove a referrer of a course, nothing else should be |
---|
| 91 | touched but only this very referrer. |
---|
[4488] | 92 | |
---|
| 93 | |
---|
[5978] | 94 | As correct deletion of referrer needs support of catalogs (we lookup |
---|
| 95 | catalogs to find referrers), we need a fully blown ZODB here, |
---|
[7811] | 96 | including an instance of :class:`waeup.kofa.app.University`, as this is our |
---|
[4488] | 97 | `ISite` object where the catalogs are stored. |
---|
| 98 | |
---|
| 99 | We create a complete university tree, starting with a |
---|
[12966] | 100 | :class:`University` instance and going down to courses, certificates |
---|
[4488] | 101 | and course certificates: |
---|
| 102 | |
---|
[7811] | 103 | >>> from waeup.kofa.app import University |
---|
[4488] | 104 | >>> root = getRootFolder() |
---|
| 105 | >>> root['app'] = University() |
---|
| 106 | |
---|
| 107 | We have to set the site, to enable proper working of catalogs and |
---|
| 108 | `IObjectRemoved` handlers: |
---|
| 109 | |
---|
| 110 | >>> from zope.app.component.hooks import setSite |
---|
| 111 | >>> setSite(root['app']) |
---|
| 112 | |
---|
| 113 | >>> from zope.component import createObject |
---|
| 114 | >>> faculty = createObject('waeup.Faculty') |
---|
| 115 | >>> faculty.code = 'FAC' |
---|
[4749] | 116 | >>> root['app']['faculties'].addFaculty(faculty) |
---|
| 117 | >>> fac = root['app']['faculties']['FAC'] |
---|
[4488] | 118 | |
---|
| 119 | >>> dept = createObject('waeup.Department') |
---|
| 120 | >>> dept.code = 'DEPT' |
---|
[4749] | 121 | >>> root['app']['faculties']['FAC'].addDepartment(dept) |
---|
[4488] | 122 | |
---|
| 123 | >>> course = createObject('waeup.Course') |
---|
| 124 | >>> course.code = 'CRS1' |
---|
| 125 | >>> fac['DEPT'].courses.addCourse(course) |
---|
| 126 | |
---|
| 127 | Now let's add another department, which will also hold a course: |
---|
| 128 | |
---|
| 129 | >>> dept2 = createObject('waeup.Department') |
---|
| 130 | >>> dept2.code = 'DEPT2' |
---|
[4749] | 131 | >>> root['app']['faculties']['FAC'].addDepartment(dept2) |
---|
[4488] | 132 | |
---|
| 133 | >>> course = createObject('waeup.Course') |
---|
| 134 | >>> course.code = 'CRS2' |
---|
| 135 | >>> fac['DEPT2'].courses.addCourse(course) |
---|
| 136 | |
---|
[5978] | 137 | Finally, we add a certificate with referrers (certificate courses) to |
---|
[4488] | 138 | the created courses: |
---|
| 139 | |
---|
| 140 | >>> cert = createObject('waeup.Certificate') |
---|
| 141 | >>> cert.code = 'CERT' |
---|
| 142 | >>> fac['DEPT'].certificates.addCertificate(cert) |
---|
| 143 | |
---|
[8920] | 144 | >>> fac['DEPT'].certificates['CERT'].addCertCourse( |
---|
[4488] | 145 | ... fac['DEPT'].courses['CRS1']) |
---|
| 146 | |
---|
[8920] | 147 | >>> fac['DEPT'].certificates['CERT'].addCertCourse( |
---|
[4488] | 148 | ... fac['DEPT2'].courses['CRS2']) |
---|
| 149 | |
---|
| 150 | We add the latter course again, but this time for the second level: |
---|
| 151 | |
---|
[8920] | 152 | >>> fac['DEPT'].certificates['CERT'].addCertCourse( |
---|
[4488] | 153 | ... fac['DEPT2'].courses['CRS2'], level=200) |
---|
| 154 | |
---|
[5978] | 155 | So, we have three referrers in our certificate of two different |
---|
[4488] | 156 | courses stored somewhere in the application: |
---|
| 157 | |
---|
| 158 | >>> list(fac['DEPT'].certificates['CERT']) |
---|
| 159 | [u'CRS1_100', u'CRS2_100', u'CRS2_200'] |
---|
| 160 | |
---|
[5978] | 161 | Both, the courses themselves and the referrers of them, are the same |
---|
[4488] | 162 | (not only equal): |
---|
| 163 | |
---|
[4749] | 164 | >>> a = root['app']['faculties']['FAC']['DEPT'] |
---|
[4488] | 165 | >>> b1 = a.courses['CRS1'] |
---|
| 166 | >>> b2 = a.certificates['CERT']['CRS1_100'].course |
---|
| 167 | >>> b1 is b2 |
---|
| 168 | True |
---|
| 169 | |
---|
[5978] | 170 | If we remove the course, also the select item in the certificate course |
---|
[4488] | 171 | will be gone: |
---|
| 172 | |
---|
| 173 | >>> list(a.certificates['CERT']) |
---|
| 174 | [u'CRS1_100', u'CRS2_100', u'CRS2_200'] |
---|
| 175 | |
---|
| 176 | >>> list(a.courses) |
---|
| 177 | [u'CRS1'] |
---|
| 178 | |
---|
[4749] | 179 | >>> del root['app']['faculties']['FAC']['DEPT'].courses['CRS1'] |
---|
[4488] | 180 | >>> list(a.certificates['CERT']) |
---|
| 181 | [u'CRS2_100', u'CRS2_200'] |
---|
| 182 | |
---|
| 183 | >>> list(a.courses) |
---|
| 184 | [] |
---|
| 185 | |
---|
[5978] | 186 | If we remove a referrer of a course, this will not touch the course |
---|
| 187 | nor other referrers: |
---|
[4488] | 188 | |
---|
| 189 | >>> del fac['DEPT'].certificates['CERT']['CRS2_200'] |
---|
| 190 | >>> list(a.certificates['CERT']) |
---|
| 191 | [u'CRS2_100'] |
---|
| 192 | |
---|
| 193 | >>> list(fac['DEPT2'].courses) |
---|
| 194 | [u'CRS2'] |
---|
[9828] | 195 | |
---|
| 196 | If a single course is registered for several levels in a single |
---|
| 197 | certificate, this will not block things if the course is removed |
---|
| 198 | (regression test): |
---|
| 199 | |
---|
| 200 | We add the same course as a reference for level 200 and level 300: |
---|
| 201 | |
---|
| 202 | >>> fac['DEPT'].certificates['CERT'].addCertCourse( |
---|
| 203 | ... fac['DEPT2'].courses['CRS2'], level=200) |
---|
| 204 | >>> fac['DEPT'].certificates['CERT'].addCertCourse( |
---|
| 205 | ... fac['DEPT2'].courses['CRS2'], level=300) |
---|
| 206 | |
---|
| 207 | Deleting the course (thus also the refererrers) will work: |
---|
| 208 | |
---|
| 209 | >>> del root['app']['faculties']['FAC']['DEPT2'].courses['CRS2'] |
---|
| 210 | |
---|
| 211 | Now all references are gone: |
---|
| 212 | |
---|
| 213 | >>> list(a.certificates['CERT']) |
---|
| 214 | [] |
---|
| 215 | |
---|
| 216 | and also the course itself vanished: |
---|
| 217 | |
---|
| 218 | >>> list(fac['DEPT2'].courses) |
---|
| 219 | [] |
---|
[12920] | 220 | |
---|
| 221 | |
---|