| |
67 |
def SplitOldNew(record):
|
| |
68 |
'''Database records contain the current and perhaps the old value of each field
|
| |
69 |
This routine separates the new and old values
|
| |
70 |
'''
|
| |
71 |
old = {}
|
| |
72 |
new = {}
|
| |
73 |
for k, v in record.iteritems():
|
| |
74 |
if k and k[0] == '_':
|
| |
75 |
old[k[1:]] = v
|
| |
76 |
else:
|
| |
77 |
new[k] = v
|
| |
78 |
return old, new
|
| |
79 |
|
| |
80 |
def LocalRowsAdded():
|
| |
81 |
"""Have we added any rows that the server doesn't know about?"""
|
| |
82 |
count = 0
|
| |
83 |
old_nextID, new_nextID = SplitOldNew(Data.NextID)
|
| |
84 |
for k, v in new_nextID.iteritems():
|
| |
85 |
old = old_nextID.get(k) or 1
|
| |
86 |
if old < v:
|
| |
87 |
count += v - old
|
| |
88 |
return count
|
| |
89 |
|
| |
90 |
# this should be returned by a function in Data
|
| |
91 |
# using 'table/column': table (same format as 'ForeignKey' table)
|
| |
92 |
# table and id in report rows needs special treatment <- IMPORTANT - To Do
|
| |
93 |
# (includes all of the non-standard foreign key names)
|
| |
94 |
foreign_key_xref = {
|
| |
95 |
'Dependency/PrerequisiteID': 'Task',
|
| |
96 |
'MeasurementDependency/PriorMeasurementID': 'Measurement',
|
| |
97 |
'ResourceGrouping/ResourceGroupID': 'Resource',
|
| |
98 |
'Report/FirstRow': 'ReportRow',
|
| |
99 |
'ReportRow/NextRow': 'ReportRow',
|
| |
100 |
'Report/FirstColumn': 'ReportColumn',
|
| |
101 |
'ReportColumn/NextColumn': 'ReportColumn',
|
| |
102 |
'ReportType/Also': 'ReportType',
|
| |
103 |
}
|
| |
104 |
|
| |
105 |
def ForeignKeyColumn(tname, cname, row): # only called if the value is known to be int??
|
| |
106 |
''' is this a foreign key column?'''
|
| |
107 |
tc = '/'.join((tname, cname))
|
| |
108 |
if tc in foreign_key_xref: # known and non-standard foreign key column names
|
| |
109 |
table = foreign_key_xref[tc]
|
| |
110 |
elif cname[-2:] == 'ID' and cname[:1] != '_': # follow foreign key naming conventions
|
| |
111 |
if cname == 'TableID' and tname == 'ReportRow': # report rows are special case
|
| |
112 |
table = row.get('TableName')
|
| |
113 |
# if not value or not table: return "" # continue # skip invalid combinations
|
| |
114 |
if not row.get(cname) or not table: return "" # continue # skip invalid combinations
|
| |
115 |
else: # this is the normal case
|
| |
116 |
table = cname[:-2] # this might still be an alias (checked later)
|
| |
117 |
elif tname == 'Report' and cname == 'SelectValue': # another special case
|
| |
118 |
selcol = row.get('SelectColumn')
|
| |
119 |
if selcol and len(selcol) > 2 and selcol[-2:] == 'ID':
|
| |
120 |
table = selcol[:-2] # this works for 'ProjectID', what other values can be here?
|
| |
121 |
# if debug: print "Changed SelectValue %s from %d" % (selcol, value)
|
| |
122 |
# if debug: print "Changed SelectValue %s from %d" % (selcol, row.get(cname))
|
| |
123 |
else:
|
| |
124 |
return "" # continue
|
| |
125 |
else: # not a foreign key
|
| |
126 |
return "" # continue
|
| |
127 |
|
| |
128 |
table_aliases = Data.Database['TableAlias'][1]
|
| |
129 |
if table in table_aliases:
|
| |
130 |
table = table_aliases[table]
|
| |
131 |
return table
|
| |
132 |
|
| |
133 |
def AdjustIDs(old_nextID, new_nextID, new_server_nextid):
|
| |
134 |
'''
|
| |
135 |
Diff between old_nextID and new_nextID shows how many rows we added
|
| |
136 |
Diff between new_nextID and new_server_nextid shows how many rows the server added
|
| |
137 |
Call this routine only if we have added rows that the server doesn't know about.
|
| |
138 |
This routine will adjust:
|
| |
139 |
all of the new row's ids
|
| |
140 |
all of the foreign keys that point to the new rows
|
| |
141 |
'''
|
| |
142 |
# renumber our new rows, if necessary (do we test this? -- not yet)
|
| |
143 |
# set server version keys to '_'? -- probably not
|
| |
144 |
# or keep track of new rows for use FindChanges (BETTER?) -- probably not
|
| |
145 |
key_adjustment = {} # how much to add to locally assigned IDs
|
| |
146 |
for tname, v in new_nextID.iteritems():
|
| |
147 |
key_adjustment[tname] = new_server_nextid.get(tname, 1) - v
|
| |
148 |
|
| |
149 |
for tname in new_nextID: # list of all non-alias table names
|
| |
150 |
newkeys = {}
|
| |
151 |
# all IDs in table will be adjusted by the same amount
|
| |
152 |
row_id_adjustment = key_adjustment.get(tname, 0)
|
| |
153 |
server_nextid = old_nextID.get(tname, 1)
|
| |
154 |
|
| |
155 |
t = Data.Database.get(tname, {})
|
| |
156 |
for rid, row in t.iteritems(): # process each row in table
|
| |
157 |
for cname, value in row.iteritems(): # look for foreign key columns
|
| |
158 |
if not cname: continue
|
| |
159 |
if not isinstance(value, int): continue # does his work for really big numbers?
|
| |
160 |
if cname == 'ID':
|
| |
161 |
if value >= server_nextid: # this is a new row
|
| |
162 |
row[cname] = value + row_id_adjustment
|
| |
163 |
else:
|
| |
164 |
table = ForeignKeyColumn(tname, cname, row)
|
| |
165 |
if not table: continue # ?? is this right
|
| |
166 |
|
| |
167 |
fk_server_nextid = old_nextID.get(table, 1)
|
| |
168 |
if value >= fk_server_nextid: # points to a new row
|
| |
169 |
if debug: print "adjusting fk in %s at %s from %d to %d" % (tname, cname, value, value + key_adjustment.get(table, 0))
|
| |
170 |
row[cname] = value + key_adjustment.get(table, 0)
|
| |
171 |
newkeys[row['ID']] = row
|
| |
172 |
Data.Database[tname] = newkeys # update tables to use new row ids
|
| |
173 |
|
| |
174 |
## # recreate aliases <- this shouldn't be hard coded
|
| |
175 |
## Data.Database['Prerequisite'] = Data.Database['Task']
|
| |
176 |
## if Data.Database.has_key('Resource'):
|
| |
177 |
## Data.Database['ResourceGroup'] = Data.Database['Resource']
|
| |
178 |
## if Data.Database.has_key('Measurement'):
|
| |
179 |
## Data.Database['PriorMeasurement'] = Data.Database['Measurement']
|
| |
180 |
# recreate aliases
|
| |
181 |
table_aliases = Data.Database['TableAlias'][1]
|
| |
182 |
for alias, table in table_aliases.iteritems():
|
| |
183 |
if table in Data.Database: # should always be true (except for 'ID')
|
| |
184 |
Data.Database[alias] = Data.Database[table]
|
| |
185 |
|
| |
186 |
Data.PrepDatabase() # tables have been replaced, fix short cuts
|
| |
187 |
return
|
| |
188 |
|
| |
189 |
# required after AdjustIDs
|
| |
190 |
def UpdateReportWindowShortcuts():
|
| |
191 |
for k, v in Data.OpenReports.iteritems(): # update report window short cuts to database
|
| |
192 |
if k == 1:
|
| |
193 |
v.UpdatePointers(1)
|
| |
194 |
else:
|
| |
195 |
# v.UpdatePointers()
|
| |
196 |
Data.UpdateDataPointers(v.Report.table, k)
|
| |
197 |
v.Report.table.UpdateRowPointers()
|
| |
198 |
v.Report.table.UpdateColumnPointers()
|
| |
199 |
Data.RefreshReports()
|
| |
200 |
|
| |
201 |
def MergeDuplicates(changeids, serverids):
|
| |
202 |
''' changeids is a dictionary with key = (table, id), value = new id
|
| |
203 |
serverids is a dictionary with same format as database['NextID']
|
| |
204 |
but the server values indicate which records the server knows about
|
| |
205 |
'''
|
| |
206 |
# run merge on duplicates???
|
| |
207 |
# create '__' fields for changes lost? <-- TO DO
|
| |
208 |
# when? before or after changing the foreign keys
|
| |
209 |
|
| |
210 |
# change foreign keys
|
| |
211 |
old_nextID, new_nextID = SplitOldNew(Data.NextID) # just used to get a list of non-alias tables
|
| |
212 |
for tname in new_nextID:
|
| |
213 |
t = Data.Database.get(tname, {})
|
| |
214 |
for rid, row in t.iteritems():
|
| |
215 |
change = { }
|
| |
216 |
for cname, value in row.iteritems():
|
| |
217 |
if not cname or cname == 'ID': continue
|
| |
218 |
|
| |
219 |
table = ForeignKeyColumn(tname, cname, row)
|
| |
220 |
if not table: continue # ?? is this right
|
| |
221 |
|
| |
222 |
newid = changeids.get((table, value)) # need double prens? - yes
|
| |
223 |
if newid: # pointed to a dup row
|
| |
224 |
change[cname] = newid
|
| |
225 |
if change:
|
| |
226 |
if debug: print "changing a foreign key"
|
| |
227 |
change['Table'] = tname
|
| |
228 |
change['ID'] = rid
|
| |
229 |
Data.Update(change, 0) # We want these changes to be picked up by FindChanges
|
| |
230 |
|
| |
231 |
# What do we do with the old records that no one is pointing to? <- To Do
|
| |
232 |
# we need to delete them or at least change the key fields to
|
| |
233 |
# None so that they won't be found by mistake. We could clear all
|
| |
234 |
# 'xxxID' columns and 'Period'.
|
| |
235 |
|
| |
236 |
# merge duplicate records
|
| |
237 |
# give preference to lowest id - should be server copy
|
| |
238 |
for (table, source), dest in changeids.iteritems():
|
| |
239 |
t = Data.Database[table]
|
| |
240 |
for k, v in t[source].iteritems():
|
| |
241 |
if k.startswith('_') or k == 'ID': continue
|
| |
242 |
if t[dest].get(k) != v: # should save varying values even if the old k doesn't exist
|
| |
243 |
t[dest]['__' + k] = v # saving all changed prior values in the record??? - is this the right action??
|
| |
244 |
# completely clear out the duplicate
|
| |
245 |
oldnextid = serverids.get('_' + table) or 0 # same format as database['NextID'] table
|
| |
246 |
if source >= oldnextid: # delete it if the server doesn't know about it
|
| |
247 |
if debug: print "deleting the duplicate record in %s with id %s" % (table, source)
|
| |
248 |
del t[source] # should test whether local or server? maybe handle them differently?
|
| |
249 |
else: # otherwise change all of it's fields to None (normally this shouldn't happen)
|
| |
250 |
change = {}
|
| |
251 |
for k in t[source].keys():
|
| |
252 |
if k.startswith('_'): continue # don't change our understanding of the server's values
|
| |
253 |
change[k] = None
|
| |
254 |
change['Table'] = table
|
| |
255 |
change['ID'] = source
|
| |
256 |
change['zzStatus'] = 'deleted'
|
| |
257 |
Data.Update(change, 0) # We want these changes to be picked up by FindChanges
|
| |
258 |
|
| |
259 |
def EditChanges(changes):
|
| |
260 |
""" Make sure all of the change can be applied before applying any.
|
| |
261 |
Currently tests the following:
|
| |
262 |
- Does the specified table exist?
|
| |
263 |
- Does the table specify an ID number?
|
| |
264 |
|
| |
265 |
Changes from the server may include row numbers higher than the numbers we just
|
| |
266 |
reserved from the server. ??is this right??
|
| |
267 |
"""
|
| |
268 |
database = Data.Database # create shortcut
|
| |
269 |
bad_change = 0
|
| |
270 |
for batch in changes:
|
| |
271 |
for c in batch:
|
| |
272 |
NonesIn(c) # add backing expected Nones
|
| |
273 |
if debug: print "editting a change"
|
| |
274 |
table = c.get("Table")
|
| |
275 |
|
| |
276 |
# change the test to -- table not in NextID
|
| |
277 |
# and don't add new tables
|
| |
278 |
if not table or table in ["NextID"]: # invalid table names for changes
|
| |
279 |
if debug: print "invalid table name", repr(table)
|
| |
280 |
bad_change = 1
|
| |
281 |
continue
|
| |
282 |
if not database.has_key(table):
|
| |
283 |
# add table to database???
|
| |
284 |
Data.AddTable(table) # I think I have to assume all table names are intended
|
| |
285 |
# we should have all table names from reserved rows
|
| |
286 |
if debug: print "passed table edit"
|
| |
287 |
|
| |
288 |
id = c.get("ID")
|
| |
289 |
if not id:
|
| |
290 |
bad_change = 1
|
| |
291 |
if debug: print "failed id edit: no ID"
|
| |
292 |
continue
|
| |
293 |
if not database[table].has_key(id):
|
| |
294 |
continue # new row, don't have to check prior values
|
| |
295 |
if debug: print "passed id edit"
|
| |
296 |
|
| |
297 |
# edit to add:
|
| |
298 |
# ID shouldn't be one of the new numbers we recently reserved
|
| |
299 |
|
| |
300 |
# these edits can be more forgiving now thank's to Alex suggestion of how to
|
| |
301 |
# handle mismatches between what the client thinks and the servers actual state
|
| |
302 |
# -- Go over the required changes with Alex
|
| |
303 |
## for k, v in c.items(): # a new list so I can change the old one
|
| |
304 |
## if k in ["Table", "ID"]: continue
|
| |
305 |
## if k[0] == "_": continue
|
| |
306 |
## row = database[table][id]
|
| |
307 |
##
|
| |
308 |
## db_value = row.get("_"+k, row.get(k)) # what we think database has
|
| |
309 |
## prior_value = c.get("_" + k)
|
| |
310 |
## if prior_value != db_value: # if prior value doesn't match
|
| |
311 |
## c["__" + k] = db_value # return real prior value and set error
|
| |
312 |
## bad_change = 1
|
| |
313 |
## if debug: print "T:", table, " ID:", id, " Ours: ", db_value, " Theirs:", prior_value
|
| |
314 |
if debug: print "finished edits"
|
| |
315 |
return bad_change
|
| |
316 |
|
| |
317 |
def ApplyChanges(changes):
|
| |
318 |
""" Apply changes using method Alex and I discussed
|
| |
319 |
All of the changes have already been examined to make sure that they can be processed successfully.
|
| |
320 |
"""
|
| |
321 |
# apply all updates
|
| |
322 |
database = Data.Database
|
| |
323 |
for batch in changes:
|
| |
324 |
for c in batch:
|
| |
325 |
table = c["Table"]
|
| |
326 |
id = c["ID"]
|
| |
327 |
if id not in database[table]:
|
| |
328 |
# add row to table
|
| |
329 |
# should Data.Update do this for us?
|
| |
330 |
database[table][id] = {"ID": id}
|
| |
331 |
if database["NextID"][table] <= id: # this shouldn't happen, but just in case
|
| |
332 |
database["NextID"][table] = id + 1
|
| |
333 |
|
| |
334 |
row = database[table][id] # change applies to this row
|
| |
335 |
row_old, row_new = SplitOldNew(row)
|
| |
336 |
|
| |
337 |
# change Data.Update to ignore "_" columns, but for not just apply them???
|
| |
338 |
# no - just let update apply "_" the same as others??
|
| |
339 |
# no - update has to know about updates from the database vs. from other local changes
|
| |
340 |
|
| |
341 |
c_old, c_new = SplitOldNew(c)
|
| |
342 |
for k, v in c_new.iteritems(): # check for conflicts
|
| |
343 |
if k in ["Table", "ID"]: continue
|
| |
344 |
|
| |
345 |
db_value = row.get(k) # what we have
|
| |
346 |
new_value = v # c_new.get(k) # what the server has
|
| |
347 |
old_value = c_old.get(k) # what the server thinks we have
|
| |
348 |
|
| |
349 |
# resolve any conflicts
|
| |
350 |
# give priority to the server value but keep a copy of the client value
|
| |
351 |
if db_value != new_value:
|
| |
352 |
if (k in row_old) or (db_value != old_value): # did we change the same field?
|
| |
353 |
row["__" + k] = db_value # found a conflict
|
| |
354 |
|
| |
355 |
# use the server's values
|
| |
356 |
if new_value or new_value == 0:
|
| |
357 |
row[k] = new_value
|
| |
358 |
elif k in row:
|
| |
359 |
del row[k] # server value is null; delete old client value
|
| |
360 |
|
| |
361 |
# remove 'changed' flag from updated records
|
| |
362 |
if row.has_key("_" + k):
|
| |
363 |
del row["_" + k]
|
| |
364 |
|
| |
365 |
#Data.Update(c_new, 0) # should we not use update to apply changes?
|
| |
366 |
# PROBLEM - will the changes from the server be mistaken later
|
| |
367 |
# as changes we made???
|
| |
368 |
# We don't want them to be picked up by FindChanges
|
| |
369 |
|
| |
370 |
# Data.SetUndo("Applied Changes from Server") # To refresh display, but shouldn't allow undo??? <-- TO DO
|
| |
371 |
# PROBLEM <- if this adds rows? I don't want to add rows until after we send updates to server.
|
| |
372 |
|
| |
373 |
# issues: listed here, because this is where we change ids
|
| |
374 |
#
|
| |
375 |
# must not commit a change to server that would violate these rules
|
| |
376 |
# - ReportType and ColumnType - names must be unique
|
| |
377 |
# - Assignment and Dependency - linked pairs must be unique
|
| |
378 |
# - All of the date related tables
|
| |
379 |
#
|
| |
380 |
# I don't think it's fatal to have these duplicated in the client
|
| |
381 |
# (I know that duplicate dependency links have been created before)
|
| |
382 |
# but they should be fixed before committing to the server
|
| |
383 |
# keep the lowest numbered record, because that will be the one on the server? NOT TRUE!
|
| |
384 |
#
|
| |
385 |
# The server should probably edit to prevent updates that cause
|
| |
386 |
# these problems
|
| |
387 |
#
|
| |
388 |
# Table and ID in report rows needs special treatment <- IMPORTANT - To Do -- ?let update handle??
|
| |
389 |
|
| |
390 |
def FindDataProblems():
|
| |
391 |
''' Edits performed:
|
| |
392 |
ReportType.Name must be unique
|
| |
393 |
(ReportType.Name, ColumnType.Name) must be unique
|
| |
394 |
|
| |
395 |
This funtion identifies duplicates and returns an list of the rows that should
|
| |
396 |
be merged. It doesn't make any changes.
|
| |
397 |
'''
|
| |
398 |
changeids = {} # (table, old id): new id
|
| |
399 |
|
| |
400 |
# prevent duplicate names
|
| |
401 |
def DupNames(table, column):
|
| |
402 |
index = {}
|
| |
403 |
t = Data.Database[table]
|
| |
404 |
rowids = t.keys()
|
| |
405 |
rowids.sort()
|
| |
406 |
for k in rowids: # give precedence to lower row #'s
|
| |
407 |
name = t[k].get(column)
|
| |
408 |
if name in index:
|
| |
409 |
changeids[(table, k)] = index[name]
|
| |
410 |
else:
|
| |
411 |
index[name] = k
|
| |
412 |
|
| |
413 |
DupNames('ReportType', 'Name') # prevent duplicate report type names
|
| |
414 |
|
| |
415 |
# apply de-dup of report types to column types
|
| |
416 |
# must be done here so column types can be de-dup'ed
|
| |
417 |
# if debug: print "changeids: ", changeids
|
| |
418 |
for k, v in Data.Database['ColumnType'].iteritems():
|
| |
419 |
rtid = v.get('ReportTypeID')
|
| |
420 |
if ('ReportType', rtid) in changeids: # it is a dup
|
| |
421 |
change = {'Table': 'ColumnType', 'ID': k,
|
| |
422 |
'ReportTypeID': changeids[('ReportType', rtid)]}
|
| |
423 |
Data.Update(change, 0) # We want these changes to be picked up by FindChanges
|
| |
424 |
if debug: print "should adjust fk in ColumnType at ReportTypeID from %d to %d" % (rtid, changeids[('ReportType', rtid)])
|
| |
425 |
|
| |
426 |
# prevent duplicate links
|
| |
427 |
def DupPair(table, columna, columnb):
|
| |
428 |
if table not in Data.Database: return
|
| |
429 |
index = {}
|
| |
430 |
t = Data.Database[table]
|
| |
431 |
rowids = t.keys()
|
| |
432 |
rowids.sort()
|
| |
433 |
for k in rowids: # give precedence to lower row #'s
|
| |
434 |
a = t[k].get(columna)
|
| |
435 |
b = t[k].get(columnb)
|
| |
436 |
# should look these up in changeids before checking for duplicates <- IMPORTANT!!!
|
| |
437 |
if changeids:
|
| |
438 |
tablea = ForeignKeyColumn(table, columna, t[k])
|
| |
439 |
tableb = ForeignKeyColumn(table, columnb, t[k])
|
| |
440 |
if tablea and (tablea, a) in changeids:
|
| |
441 |
a = changeids[(tablea, a)]
|
| |
442 |
if tableb and (tableb, b) in changeids:
|
| |
443 |
b = changeids[(tableb, b)]
|
| |
444 |
link = ( a, b )
|
| |
445 |
if link in index:
|
| |
446 |
changeids[(table, k)] = index[link]
|
| |
447 |
else:
|
| |
448 |
index[link] = k
|
| |
449 |
|
| |
450 |
# specify 'table' and then the columns that as a group must be unique
|
| |
451 |
# be careful about the order, some of these may identify duplications that
|
| |
452 |
# a later command will have to know about
|
| |
453 |
DupPair('Assignment', 'TaskID', 'ResourceID')
|
| |
454 |
DupPair('Dependency', 'TaskID', 'PrerequisiteID')
|
| |
455 |
DupPair('ColumnType', 'ReportTypeID', 'Name') # prevent duplicate column names in reporttype
|
| |
456 |
DupPair('ResourceGrouping', 'ResourceID', 'ResourceGroupID') # you can only be in the same group once
|
| |
457 |
|
| |
458 |
for tablename in Data.Database.keys():
|
| |
459 |
for period in ('Day', 'Week', 'Month'):
|
| |
460 |
if tablename.endswith(period):
|
| |
461 |
key1 = tablename[:-len(period)] + 'ID'
|
| |
462 |
# key2 = period # this is wrong, should always use 'Period' -- 080101
|
| |
463 |
key2 = 'Period'
|
| |
464 |
DupPair(tablename, key1, key2)
|
| |
465 |
# example 'ProjectDay' becomes 'Project', 'ProjectID', 'Period'
|
| |
466 |
|
| |
467 |
## # maybe handle reports and columns in a separate method
|
| |
468 |
## # prevent multiple report rows from pointing to the same record
|
| |
469 |
## def DupTrio(table, columna, columnb, columnc):
|
| |
470 |
## index = {}
|
| |
471 |
## t = Data.Database[table]
|
| |
472 |
## rowids = t.keys()
|
| |
473 |
## rowids.sort()
|
| |
474 |
## for k in rowids: # give precedence to lower row #'s
|
| |
475 |
## link = ( t[k].get(columna), t[k].get(columnb), t[k].get(columnc) )
|
| |
476 |
## if link in index:
|
| |
477 |
## changeids[(table, k)] = index[link]
|
| |
478 |
## else:
|
| |
479 |
## index[link] = k
|
| |
480 |
## DupTrio('ReportRow', 'ReportID', 'TableName', 'TableID')
|
| |
481 |
|
| |
482 |
return changeids
|
| |
483 |
|
| |
484 |
def FindChanges():
|
| |
485 |
""" Identify all of the local changes in the client database since last share with server.
|
| |
486 |
Return them in the format that the server expects to see them.
|
| |
487 |
"""
|
| |
488 |
changes = [ ]
|
| |
489 |
|
| |
490 |
# tables = Data.Database.items()
|
| |
491 |
# tables.sort()
|
| |
492 |
# for tid, t in tables:
|
| |
493 |
# for tid, nid in Data.NextID.iteritems():
|
| |
494 |
old_nextID, new_nextID = SplitOldNew(Data.NextID) # just used to get a list of non-alias tables
|
| |
495 |
for tid, nid in new_nextID.iteritems():
|
| |
496 |
t = Data.Database.get(tid) or {}
|
| |
497 |
# if tid[0] == "_": continue # skip internal tables, if any
|
| |
498 |
|
| |
499 |
# do the changes need to be sent with the original table name or will aliases work?
|
| |
500 |
# problems with using aliases:
|
| |
501 |
# we don't currently tell the server about aliases we create -- PROBLEM!
|
| |
502 |
# the other clients
|
| |
503 |
|
| |
504 |
# don't remember why I had this or why it doesn't work now
|
| |
505 |
server_prior_next = server_prior_nextid.get(tid, 1)
|
| |
506 |
|
| |
507 |
# most tables can be handled like this?
|
| |
508 |
# normal[tid] = t.values() # list of values (id's are in the records)
|
| |
509 |
for id, record in t.iteritems(): # each row
|
| |
510 |
# print "id:", id, " record:", record
|
| |
511 |
# id = t.get('ID') # can I assume it is there?
|
| |
512 |
# if not id: continue # silently ignore invalid records
|
| |
513 |
|
| |
514 |
if id < server_prior_next:
|
| |
515 |
old, new = SplitOldNew(record)
|
| |
516 |
change = {} # build a change before adding to changes list
|
| |
517 |
for k, v in old.iteritems():
|
| |
518 |
if k[0] == '_': continue # skip saved conflict values
|
| |
519 |
change[k] = new.get(k)
|
| |
520 |
change["_" + k] = v
|
| |
521 |
|
| |
522 |
if change: # only include real changes
|
| |
523 |
change["Table"] = tid
|
| |
524 |
change["ID"] = id
|
| |
525 |
NonesOut(change)
|
| |
526 |
changes.append(change)
|
| |
527 |
else:
|
| |
528 |
change = record.copy()
|
| |
529 |
for k in change.keys(): # omit saved conflict values and "prior server values"
|
| |
530 |
if k.startswith('_'): # these shouldn't be here (but can be created when records are merged)
|
| |
531 |
del change[k]
|
| |
532 |
change['Table'] = tid
|
| |
533 |
NonesOut(change)
|
| |
534 |
changes.append(change)
|
| |
535 |
return changes
|
| |
536 |
|
| |
537 |
# Clear server version
|
| |
538 |
def ClearServerVersions():
|
| |
539 |
""" If we think we are in sync, clear server version data. """
|
| |
540 |
old_nextID, new_nextID = SplitOldNew(Data.NextID) # just used to get a list of non-alias tables
|
| |
541 |
for tid, nid in new_nextID.iteritems(): # each non-alias table
|
| |
542 |
t = Data.Database.get(tid) or {}
|
| |
543 |
for id, record in t.iteritems(): # each row
|
| |
544 |
for k in record.keys(): # each column
|
| |
545 |
if k[0] == '_' and k[:2] != '__': # delete just the single dash?
|
| |
546 |
del record[k]
|
| |
547 |
return
|
| |
548 |
|
| |
549 |
def RememberReports():
|
| |
550 |
'''Remember rows and columns for any changed reports'''
|
| |
551 |
report_save = {}
|
| |
552 |
report = Data.Database['Report']
|
| |
553 |
column = Data.Database['ReportColumn']
|
| |
554 |
row = Data.Database['ReportRow']
|
| |
555 |
for k, v in report.iteritems():
|
| |
556 |
delta_c = False
|
| |
557 |
delta_r = False
|
| |
558 |
cols = Data.GetColumnList(k)
|
| |
559 |
rows = Data.GetRowList(k)
|
| |
560 |
if '_FirstColumn' in v:
|
| |
561 |
delta_c = True
|
| |
562 |
else:
|
| |
563 |
for c in cols:
|
| |
564 |
if '_NextColumn' in column[c]:
|
| |
565 |
delta_c = True
|
| |
566 |
break
|
| |
567 |
if '_FirstRow' in v:
|
| |
568 |
delta_r = True
|
| |
569 |
else:
|
| |
570 |
for r in rows:
|
| |
571 |
if '_NextRow' in row[r]:
|
| |
572 |
delta_r = True
|
| |
573 |
break
|
| |
574 |
if delta_c:
|
| |
575 |
report_save[(k, 'column')] = cols
|
| |
576 |
if delta_r:
|
| |
577 |
report_save[(k, 'row')] = rows
|
| |
578 |
return report_save
|
| |
579 |
|
| |
580 |
def FixReports(report_save):
|
| |
581 |
'''If the server changed what we also changed, make sure all of our
|
| |
582 |
rows/columns still appear in the report
|
| |
583 |
|
| |
584 |
Should reconstruct what the server thinks the report looks like
|
| |
585 |
Follow links but: if underscore use it, unless double underscore
|
| |
586 |
in which case use non-underscore. Problem is that we don't clear the
|
| |
587 |
double underscore ever.
|
| |
588 |
'''
|
| |
589 |
for key, prior in report_save:
|
| |
590 |
k, which = key
|
| |
591 |
if which == 'column':
|
| |
592 |
cols = Data.GetColumnList(k)
|
| |
593 |
if prior != cols:
|
| |
594 |
Data.ReorderReportColumns(k, prior + cols) # precedence to local view
|
| |
595 |
else:
|
| |
596 |
rows = Data.GetRowList(k)
|
| |
597 |
if prior != rows:
|
| |
598 |
Data.ReorderReportRows(k, prior + rows)
|
| |
599 |
# Data.SetUndo('FixReports')
|
| |
600 |
|