Jul 26, 2012
plone.app.linkintegrity trouble
Il problema dell'integrità referenziale cancellando contenuti Plone tramite procedure Pyhton
Quando un programmatore Plone vuole cancellare una serie di documenti tramite una procedura Python, non sempre questa operazione va a buon fine. Se vi è capitato e non avete capito il perché allora provate a leggere a questo articolo; la colpa potrebbe essere del controllo di integrità referenziale.
Partiamo da un po' più lontano; mai visto questa schermata?

Beh, suppongo proprio di sì. Questo è dovuto al controllo di integrità referenziale che ha trovato un problema; cancellando Pagina 2, il link contenuto in Pagina di Test risulterà rotto, senza più il suo riferimento.
parent.manage_delObject(obj_id,)
Ve lo dico io. Il controllo in questione si attiva ugualmente e vi impedisce di cancellare l'elemento, ritornando un'eccezione (LinkIntegrityNotificationException). Forse anche questa cosa vi era nota e magari nelle vostre pocedure, essendo previdenti, gestite l’eccezione in un modo simile a questo.
try:parent.manage_delObject(obj.getId())except LinkIntegrityNotificationException:custom_log(“Non è stato possiblie cancellare il contenuto %s a causa di un problemadi integrità dei link” %obj.absolute_url())
Non crediate di essere al sicuro perché, nel caso in cui la procedura debba cancellare più di un documento, potreste incappare in un comportamento anomalo:
Invece di cancellare tutte le schede tranne quelle per cui si è trovata l’eccezione di integrità dei link, ci si ritrova che nessuna delle schede che si voleva cancellare viene cancellata.
Mi sono imbattuto in un caso del genere mentre sviluppavo una procedura per permettere la sincronizzazione di contenuti Plone su portali differenti. La procedura aveva tre fasi: aggiunta di nuovi contenuti, modifica dei contenuti già esistenti e cancellazione dei contenuti non più presenti sul portale di origine.
Il portale di destinazione però non era totalmente sotto il controllo della sincronizzazione e quindi poteva capitare che altri contenuti del portale avessero un link che puntava a una scheda che doveva essere cancellata.
Scoperto il problema, mi sono messo ad analizzarlo e ho scoperto che la causa di questa anomalia è da imputare al pacchetto plone.app.linkintegrity e più precisamente alla creazione dell’istanza della classe LinkIntegrityInfo.
Alla classe LinkIntegrityInfo è demandato il compito di salvare gli errori di integrità referenziale durante la procedura di cancellazione, per poi notificarli all’utente tramite la schermata mostrata all'inizio.
Purtroppo questa classe viene inizializzata tramite un unico parametro: context, che è la request dell’azione.
def __init__(self, context): self.context = context # the context is the request
Nel caso si sia lanciata una procedura che deve cancellare una serie di contenuti, la request è sempre la stessa e quindi per ogni contenuto che si cerca di cancellare Plone va ad analizzare la stessa istanza di questa classe.
I metodi di controllo che vengono richiamati durante la cancellazione, però, ritornano l’errore di integrità referenziale non solo se il contenuto che si sta analizzando ha questo tipo di problema: succede anche se c’è già salvato un precedente contenuto contenente un link che aveva questa problematica.
def getIntegrityBreaches(self): """ return stored information regarding link integrity breaches after removing circular references, confirmed items etc """ deleted = self.getDeletedItems() breaches = dict(self.getIntegrityInfo().get('breaches', {})) targets = breaches.keys() for target, sources in breaches.items(): # first remove deleted sources for source in list(sources): if source in targets or source in deleted: sources.remove(source) for target, sources in breaches.items(): # then remove "empty" targets if not sources or self.isConfirmedItem(target): del breaches[target] return breaches
Se non volete stare ad impazzire facendo patch su patch di queste funzioni io vi propongo una soluzione tanto semplice quanto efficace:
Mentre scrivete la vostra procedura di cancellazione ricordatevi di effettuare dei controlli preventivi.
Per ogni elemento potrete eseguire il controllo riportato nel box sotto e fare la cancellazione solo quando sarete sicuri che non verrà rilevato un errore di integrità referenziale; in caso contrario eseguite il log dell’errore.
rc = getToolByName(self, 'reference_catalog')
for obj in to_delete_obj: back_references = rc.getBackReferences(obj, relationship="isReferencing") if not back_references == []: for back_ref in back_references: errorlog('Errore durante la cancellazione; l’oggetto è referenziato da: %s' %'/'.join(back_ref.getSourceObject().getPhysicalPath())) else: try: obj.aq_parent.manage_delObjects(obj.id) transaction.commit()
Sicuramente questa non è l'unica soluzione al problema, ma mi ha consentito di aggirare velocemente l'ostacolo e di procedere con successo.
Perciò mi sento di consigliarvi di mettere in conto questo tipo di controllo ogni qual volta avrete a che fare con una procedura di cancellazione. Questo vi permetterà di evitare di inserire debito tecnico nel codice,si ridurrà la probabilità di dover eseguire bug fixing e quindi, cosa più importante, vi farà risparmiare tempo e denaro.