mirror of
https://github.com/lucaspalomodevelop/indico-plugins.git
synced 2026-03-13 07:29:39 +00:00
LiveSync: Adapt to new objects
This commit is contained in:
parent
4f59e4b2a6
commit
e5d5926780
@ -21,9 +21,14 @@ from collections import defaultdict
|
||||
from flask import g
|
||||
|
||||
from indico.core import signals
|
||||
from indico.core.db.sqlalchemy.protection import ProtectionMode
|
||||
from indico.modules.events import Event
|
||||
from indico.modules.events.contributions.models.contributions import Contribution
|
||||
from indico.modules.events.contributions.models.subcontributions import SubContribution
|
||||
from indico.modules.events.sessions import Session
|
||||
from indico.util.event import unify_event_args
|
||||
from MaKaC.accessControl import AccessController
|
||||
from MaKaC.conference import ConferenceHolder, Conference, Contribution, SubContribution, Category, Session
|
||||
from MaKaC.conference import Category, ConferenceHolder, Conference
|
||||
|
||||
from indico_livesync.models.queue import LiveSyncQueueEntry, ChangeType
|
||||
from indico_livesync.util import obj_ref, is_ref_excluded
|
||||
@ -37,30 +42,34 @@ def connect_signals(plugin):
|
||||
plugin.connect(signals.category.moved, _moved)
|
||||
plugin.connect(signals.event.moved, _moved)
|
||||
# created
|
||||
plugin.connect(signals.category.created, _created)
|
||||
plugin.connect(signals.event.created, _created)
|
||||
plugin.connect(signals.event.contribution_created, _created)
|
||||
plugin.connect(signals.event.subcontribution_created, _created)
|
||||
# deleted
|
||||
plugin.connect(signals.category.deleted, _deleted)
|
||||
plugin.connect(signals.event.deleted, _deleted)
|
||||
plugin.connect(signals.event.contribution_deleted, _deleted)
|
||||
plugin.connect(signals.event.subcontribution_deleted, _deleted)
|
||||
# data
|
||||
plugin.connect(signals.category.data_changed, _data_changed)
|
||||
plugin.connect(signals.event.data_changed, _data_changed)
|
||||
plugin.connect(signals.event.contribution_data_changed, _data_changed)
|
||||
plugin.connect(signals.event.subcontribution_data_changed, _data_changed)
|
||||
# updated
|
||||
plugin.connect(signals.event.data_changed, _updated)
|
||||
plugin.connect(signals.event.contribution_updated, _updated)
|
||||
plugin.connect(signals.event.subcontribution_updated, _updated)
|
||||
# timetable
|
||||
plugin.connect(signals.event.timetable_entry_created, _timetable_changed)
|
||||
plugin.connect(signals.event.timetable_entry_updated, _timetable_changed)
|
||||
plugin.connect(signals.event.timetable_entry_deleted, _timetable_changed)
|
||||
# protection
|
||||
plugin.connect(signals.category.protection_changed, _protection_changed)
|
||||
plugin.connect(signals.event.protection_changed, _protection_changed)
|
||||
plugin.connect(signals.event.contribution_protection_changed, _protection_changed)
|
||||
plugin.connect(signals.category.protection_changed, _protection_changed_legacy)
|
||||
plugin.connect(signals.event.protection_changed, _protection_changed_legacy)
|
||||
plugin.connect(signals.acl.protection_changed, _protection_changed, sender=Session)
|
||||
plugin.connect(signals.acl.protection_changed, _protection_changed, sender=Contribution)
|
||||
# ACLs
|
||||
plugin.connect(signals.acl.access_granted, _acl_changed)
|
||||
plugin.connect(signals.acl.access_revoked, _acl_changed)
|
||||
plugin.connect(signals.acl.modification_granted, _acl_changed)
|
||||
plugin.connect(signals.acl.modification_revoked, _acl_changed)
|
||||
plugin.connect(signals.acl.entry_changed, _event_acl_changed, sender=Event)
|
||||
plugin.connect(signals.acl.access_granted, _acl_changed_legacy)
|
||||
plugin.connect(signals.acl.access_revoked, _acl_changed_legacy)
|
||||
plugin.connect(signals.acl.modification_granted, _acl_changed_legacy)
|
||||
plugin.connect(signals.acl.modification_revoked, _acl_changed_legacy)
|
||||
plugin.connect(signals.acl.entry_changed, _acl_entry_changed, sender=Event)
|
||||
plugin.connect(signals.acl.entry_changed, _acl_entry_changed, sender=Session)
|
||||
plugin.connect(signals.acl.entry_changed, _acl_entry_changed, sender=Contribution)
|
||||
# domain access
|
||||
plugin.connect(signals.category.domain_access_granted, _domain_changed)
|
||||
plugin.connect(signals.category.domain_access_revoked, _domain_changed)
|
||||
@ -86,33 +95,59 @@ def _moved(obj, old_parent, new_parent, **kwargs):
|
||||
_register_change(obj, ChangeType.protection_changed)
|
||||
|
||||
|
||||
def _created(obj, parent, **kwargs):
|
||||
_register_change(parent, ChangeType.data_changed)
|
||||
def _created(obj, **kwargs):
|
||||
if isinstance(obj, Event):
|
||||
parent = None
|
||||
elif isinstance(obj, Contribution):
|
||||
parent = obj.event_new
|
||||
elif isinstance(obj, SubContribution):
|
||||
parent = obj.contribution
|
||||
else:
|
||||
raise TypeError('Unexpected object: {}'.format(type(obj).__name__))
|
||||
if parent:
|
||||
_register_change(parent, ChangeType.data_changed)
|
||||
_register_change(obj, ChangeType.created)
|
||||
|
||||
|
||||
def _deleted(obj, **kwargs):
|
||||
parent = kwargs.pop('parent', None)
|
||||
_register_deletion(obj, parent)
|
||||
_register_deletion(obj)
|
||||
|
||||
|
||||
def _data_changed(obj, **kwargs):
|
||||
def _updated(obj, **kwargs):
|
||||
_register_change(obj, ChangeType.data_changed)
|
||||
|
||||
|
||||
def _protection_changed(obj, old, new, **kwargs):
|
||||
def _timetable_changed(entry, **kwargs):
|
||||
_register_change(entry.event_new, ChangeType.data_changed)
|
||||
|
||||
|
||||
def _protection_changed_legacy(obj, old, new, **kwargs):
|
||||
if new == 0: # inheriting
|
||||
new = 1 if obj.isProtected() else -1
|
||||
if old != new:
|
||||
_register_change(obj, ChangeType.protection_changed)
|
||||
|
||||
|
||||
def _acl_changed(obj, principal, **kwargs):
|
||||
def _protection_changed(sender, obj, **kwargs):
|
||||
if isinstance(obj, Session):
|
||||
_register_change(obj.event_new, ChangeType.protection_changed)
|
||||
else:
|
||||
_register_change(obj, ChangeType.protection_changed)
|
||||
|
||||
|
||||
def _acl_changed_legacy(obj, **kwargs):
|
||||
_handle_acl_change(obj)
|
||||
|
||||
|
||||
def _event_acl_changed(sender, obj, **kwargs):
|
||||
_register_change(obj.as_legacy, ChangeType.protection_changed)
|
||||
def _acl_entry_changed(sender, obj, **kwargs):
|
||||
if isinstance(obj, Session):
|
||||
# if a session acl is changed we need to update all inheriting
|
||||
# contributions in that session
|
||||
for contrib in obj.contributions:
|
||||
if contrib.protection_mode == ProtectionMode.inheriting:
|
||||
_register_change(contrib, ChangeType.protection_changed)
|
||||
else:
|
||||
_register_change(obj, ChangeType.protection_changed)
|
||||
|
||||
|
||||
def _domain_changed(obj, **kwargs):
|
||||
@ -120,18 +155,13 @@ def _domain_changed(obj, **kwargs):
|
||||
|
||||
|
||||
def _note_changed(note, **kwargs):
|
||||
obj = note.linked_object
|
||||
if isinstance(obj, Session):
|
||||
obj = ConferenceHolder().getById(note.event_id)
|
||||
_register_change(obj, ChangeType.data_changed)
|
||||
_register_change(note.object, ChangeType.data_changed)
|
||||
|
||||
|
||||
def _attachment_changed(attachment_or_folder, **kwargs):
|
||||
folder = getattr(attachment_or_folder, 'folder', attachment_or_folder)
|
||||
obj = folder.linked_object
|
||||
if isinstance(obj, Session):
|
||||
obj = ConferenceHolder().getById(folder.event_id)
|
||||
_register_change(obj, ChangeType.data_changed)
|
||||
if not isinstance(folder.object, Category):
|
||||
_register_change(folder.object.event_new, ChangeType.data_changed)
|
||||
|
||||
|
||||
def _apply_changes(sender, **kwargs):
|
||||
@ -149,32 +179,28 @@ def _clear_changes(sender, **kwargs):
|
||||
del g.livesync_changes
|
||||
|
||||
|
||||
def _handle_acl_change(obj, child=False):
|
||||
if isinstance(obj, (Conference, Contribution, SubContribution, Category)):
|
||||
# if it was a child, emit data_changed instead
|
||||
if child:
|
||||
def _handle_acl_change(obj):
|
||||
if isinstance(obj, Category):
|
||||
_register_change(obj, ChangeType.protection_changed)
|
||||
elif isinstance(obj, Conference):
|
||||
if obj.getOwner():
|
||||
_register_change(obj, ChangeType.data_changed)
|
||||
else:
|
||||
_register_change(obj, ChangeType.protection_changed)
|
||||
elif isinstance(obj, Session):
|
||||
owner = obj.getOwner()
|
||||
if owner:
|
||||
_register_change(owner, ChangeType.data_changed)
|
||||
elif isinstance(obj, AccessController):
|
||||
_handle_acl_change(obj.getOwner(), child=False)
|
||||
_handle_acl_change(obj.getOwner())
|
||||
else:
|
||||
_handle_acl_change(obj.getOwner(), child=True)
|
||||
raise TypeError('Unexpected object: {}'.format(type(obj).__name__))
|
||||
|
||||
|
||||
def _register_deletion(obj, parent):
|
||||
def _register_deletion(obj):
|
||||
_init_livesync_g()
|
||||
g.livesync_changes[obj_ref(obj, parent)].add(ChangeType.deleted)
|
||||
g.livesync_changes[obj_ref(obj)].add(ChangeType.deleted)
|
||||
|
||||
|
||||
@unify_event_args
|
||||
def _register_change(obj, action):
|
||||
if not isinstance(obj, Category):
|
||||
event = obj.getConference()
|
||||
if event is None or ConferenceHolder().getById(event.id, True) is None or event.getOwner() is None:
|
||||
event = obj.event_new
|
||||
if event is None or event.is_deleted or event.as_legacy is None or event.as_legacy.getOwner() is None:
|
||||
# When deleting an event we get data change signals afterwards. We can simple ignore them.
|
||||
# When moving an event it's even worse, we get a data change notification in the middle of the move while
|
||||
# the event has no category...
|
||||
@ -185,5 +211,4 @@ def _register_change(obj, action):
|
||||
|
||||
|
||||
def _init_livesync_g():
|
||||
if not hasattr(g, 'livesync_changes'):
|
||||
g.livesync_changes = defaultdict(set)
|
||||
g.setdefault('livesync_changes', defaultdict(set))
|
||||
|
||||
@ -18,12 +18,14 @@ from __future__ import unicode_literals
|
||||
|
||||
from flask_pluginengine import current_plugin
|
||||
|
||||
from indico.modules.users import User
|
||||
from MaKaC.accessControl import AccessWrapper
|
||||
from MaKaC.common.output import outputGenerator
|
||||
from MaKaC.common.xmlGen import XMLGen
|
||||
from indico.modules.users import User
|
||||
from MaKaC.conference import Category
|
||||
|
||||
from indico_livesync import SimpleChange
|
||||
from indico_livesync.models.queue import EntryType
|
||||
from indico_livesync.util import make_compound_id, obj_deref, obj_ref
|
||||
|
||||
|
||||
@ -74,19 +76,19 @@ class MARCXMLGenerator:
|
||||
xg.closeTag(b'datafield')
|
||||
xg.closeTag(b'record')
|
||||
self.xml_generator.xml += xg.xml
|
||||
elif ref['type'] in {'event', 'contribution', 'subcontribution'}:
|
||||
elif ref['type'] in {EntryType.event, EntryType.contribution, EntryType.subcontribution}:
|
||||
obj = obj_deref(ref)
|
||||
if obj is None:
|
||||
raise ValueError('Cannot add deleted object')
|
||||
elif not obj.getOwner():
|
||||
elif isinstance(obj, Category) and not obj.getOwner():
|
||||
raise ValueError('Cannot add object without owner: {}'.format(obj))
|
||||
if ref['type'] == 'event':
|
||||
if ref['type'] == EntryType.event:
|
||||
self.xml_generator.xml += self._event_to_marcxml(obj)
|
||||
elif ref['type'] == 'contribution':
|
||||
elif ref['type'] == EntryType.contribution:
|
||||
self.xml_generator.xml += self._contrib_to_marcxml(obj)
|
||||
elif ref['type'] == 'subcontribution':
|
||||
elif ref['type'] == EntryType.subcontribution:
|
||||
self.xml_generator.xml += self._subcontrib_to_marcxml(obj)
|
||||
elif ref['type'] == 'category':
|
||||
elif ref['type'] == EntryType.category:
|
||||
pass # we don't send category updates
|
||||
else:
|
||||
raise ValueError('unknown object ref: {}'.format(ref['type']))
|
||||
|
||||
@ -25,7 +25,7 @@ from indico.util.struct.enum import IndicoEnum
|
||||
from MaKaC.conference import CategoryManager
|
||||
|
||||
from indico_livesync.models.agents import LiveSyncAgent
|
||||
from indico_livesync.util import obj_deref
|
||||
from indico_livesync.util import obj_deref, obj_ref
|
||||
|
||||
|
||||
class ChangeType(int, IndicoEnum):
|
||||
@ -191,7 +191,7 @@ class LiveSyncQueueEntry(db.Model):
|
||||
@property
|
||||
def object_ref(self):
|
||||
"""Returns the reference of the changed object"""
|
||||
return ImmutableDict(type=self.type.name, category_id=self.category_id, event_id=self.event_id,
|
||||
return ImmutableDict(type=self.type, category_id=self.category_id, event_id=self.event_id,
|
||||
contrib_id=self.contrib_id, subcontrib_id=self.subcontrib_id)
|
||||
|
||||
@return_ascii
|
||||
@ -203,18 +203,18 @@ class LiveSyncQueueEntry(db.Model):
|
||||
return format_repr(self, 'agent', 'id', 'type', 'change', _text=ref_repr)
|
||||
|
||||
@classmethod
|
||||
def create(cls, changes, obj_ref):
|
||||
def create(cls, changes, ref):
|
||||
"""Creates a new change in all queues
|
||||
|
||||
:param changes: the change types, an iterable containing
|
||||
:class:`ChangeType`
|
||||
:param obj_ref: the object reference (returned by `obj_ref`)
|
||||
:param ref: the object reference (returned by `obj_ref`)
|
||||
of the changed object
|
||||
"""
|
||||
obj_ref = dict(obj_ref)
|
||||
ref = dict(ref)
|
||||
for agent in LiveSyncAgent.find():
|
||||
for change in changes:
|
||||
entry = cls(agent=agent, change=change, **obj_ref)
|
||||
entry = cls(agent=agent, change=change, **ref)
|
||||
db.session.add(entry)
|
||||
db.session.flush()
|
||||
|
||||
@ -224,26 +224,14 @@ class LiveSyncQueueEntry(db.Model):
|
||||
The only field of the yielded items that should be used are
|
||||
`type`, `object` and `object_ref`.
|
||||
"""
|
||||
if self.type not in {'category', 'event', 'contribution'}:
|
||||
if self.type not in {EntryType.category, EntryType.event, EntryType.contribution}:
|
||||
return
|
||||
data = {'change': self.change,
|
||||
'category_id': self.category_id, 'event_id': self.event_id,
|
||||
'contrib_id': self.contrib_id, 'subcontrib_id': self.subcontrib_id}
|
||||
if self.type == 'category':
|
||||
if self.type == EntryType.category:
|
||||
for event in self.object.iterAllConferences():
|
||||
new_data = dict(data)
|
||||
new_data['type'] = 'event'
|
||||
new_data['event_id'] = event.getId()
|
||||
yield LiveSyncQueueEntry(**new_data)
|
||||
elif self.type == 'event':
|
||||
for contrib in self.object.iterContributions():
|
||||
new_data = dict(data)
|
||||
new_data['type'] = 'contribution'
|
||||
new_data['contrib_id'] = contrib.getId()
|
||||
yield LiveSyncQueueEntry(**new_data)
|
||||
elif self.type == 'contribution':
|
||||
for subcontrib in self.object.iterSubContributions():
|
||||
new_data = dict(data)
|
||||
new_data['type'] = 'subcontribution'
|
||||
new_data['subcontrib_id'] = subcontrib.getId()
|
||||
yield LiveSyncQueueEntry(**new_data)
|
||||
yield LiveSyncQueueEntry(change=self.change, **obj_ref(event))
|
||||
elif self.type == EntryType.event:
|
||||
for contrib in self.object.contributions:
|
||||
yield LiveSyncQueueEntry(change=self.change, **obj_ref(contrib))
|
||||
elif self.type == EntryType.contribution:
|
||||
for subcontrib in self.object.subcontributions:
|
||||
yield LiveSyncQueueEntry(change=self.change, **obj_ref(subcontrib))
|
||||
|
||||
@ -20,7 +20,7 @@ from collections import defaultdict
|
||||
|
||||
from indico.util.struct.enum import IndicoEnum
|
||||
|
||||
from indico_livesync.models.queue import ChangeType
|
||||
from indico_livesync.models.queue import ChangeType, EntryType
|
||||
|
||||
|
||||
class SimpleChange(int, IndicoEnum):
|
||||
@ -43,17 +43,17 @@ def process_records(records):
|
||||
# Probably they are updates for objects that got deleted afterwards.
|
||||
continue
|
||||
if record.change == ChangeType.created:
|
||||
if record.type != 'category':
|
||||
changes[record.object_ref] |= SimpleChange.created
|
||||
assert record.type != EntryType.category
|
||||
changes[record.object_ref] |= SimpleChange.created
|
||||
elif record.change == ChangeType.deleted:
|
||||
if record.type != 'category':
|
||||
changes[record.object_ref] |= SimpleChange.deleted
|
||||
assert record.type != EntryType.category
|
||||
changes[record.object_ref] |= SimpleChange.deleted
|
||||
elif record.change in {ChangeType.moved, ChangeType.protection_changed}:
|
||||
for ref in _cascade(record):
|
||||
changes[ref] |= SimpleChange.updated
|
||||
elif record.change == ChangeType.data_changed:
|
||||
if record.type != 'category':
|
||||
changes[record.object_ref] |= SimpleChange.updated
|
||||
assert record.type != EntryType.category
|
||||
changes[record.object_ref] |= SimpleChange.updated
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
@ -20,26 +20,27 @@ from datetime import timedelta
|
||||
|
||||
from werkzeug.datastructures import ImmutableDict
|
||||
|
||||
from indico.modules.events import Event
|
||||
from indico.modules.events.contributions.models.contributions import Contribution
|
||||
from indico.modules.events.contributions.models.subcontributions import SubContribution
|
||||
from indico.util.caching import memoize_request
|
||||
from indico.util.date_time import now_utc
|
||||
from MaKaC.conference import Conference, Contribution, SubContribution, Category, CategoryManager, ConferenceHolder
|
||||
from MaKaC.conference import Category, CategoryManager, Conference
|
||||
|
||||
|
||||
def obj_ref(obj, parent=None):
|
||||
def obj_ref(obj):
|
||||
"""Returns a tuple identifying a category/event/contrib/subcontrib"""
|
||||
from indico_livesync.models.queue import EntryType
|
||||
if isinstance(obj, Category):
|
||||
ref = {'type': 'category', 'category_id': obj.id}
|
||||
ref = {'type': EntryType.category, 'category_id': obj.id}
|
||||
elif isinstance(obj, Event):
|
||||
ref = {'type': EntryType.event, 'event_id': obj.id}
|
||||
elif isinstance(obj, Conference):
|
||||
ref = {'type': 'event', 'category_id': obj.getOwner().id, 'event_id': obj.id}
|
||||
ref = {'type': EntryType.event, 'event_id': int(obj.id)}
|
||||
elif isinstance(obj, Contribution):
|
||||
event = parent or obj.getConference()
|
||||
ref = {'type': 'contribution', 'category_id': event.getOwner().id, 'event_id': event.id, 'contrib_id': obj.id}
|
||||
ref = {'type': EntryType.contribution, 'contrib_id': obj.id}
|
||||
elif isinstance(obj, SubContribution):
|
||||
contrib = parent or obj.getContribution()
|
||||
event = contrib.getConference()
|
||||
ref = {'type': 'subcontribution',
|
||||
'category_id': event.getOwner().id,
|
||||
'event_id': event.id, 'contrib_id': contrib.id, 'subcontrib_id': obj.id}
|
||||
ref = {'type': EntryType.subcontribution, 'subcontrib_id': obj.id}
|
||||
else:
|
||||
raise ValueError('Unexpected object: {}'.format(obj.__class__.__name__))
|
||||
return ImmutableDict(ref)
|
||||
@ -47,35 +48,32 @@ def obj_ref(obj, parent=None):
|
||||
|
||||
def obj_deref(ref):
|
||||
"""Returns the object identified by `ref`"""
|
||||
if ref['type'] == 'category':
|
||||
try:
|
||||
return CategoryManager().getById(ref['category_id'])
|
||||
except KeyError:
|
||||
return None
|
||||
elif ref['type'] in {'event', 'contribution', 'subcontribution'}:
|
||||
event = ConferenceHolder().getById(ref['event_id'], quiet=True)
|
||||
if ref['type'] == 'event' or not event:
|
||||
return event
|
||||
contrib = event.getContributionById(ref['contrib_id'])
|
||||
if ref['type'] == 'contribution' or not contrib:
|
||||
return contrib
|
||||
return contrib.getSubContributionById(ref['subcontrib_id'])
|
||||
from indico_livesync.models.queue import EntryType
|
||||
if ref['type'] == EntryType.category:
|
||||
return CategoryManager().getById(ref['category_id'], True)
|
||||
elif ref['type'] == EntryType.event:
|
||||
return Event.get(ref['event_id'])
|
||||
elif ref['type'] == EntryType.contribution:
|
||||
return Contribution.get(ref['contrib_id'])
|
||||
elif ref['type'] == EntryType.subcontribution:
|
||||
return SubContribution.get(ref['subcontrib_id'])
|
||||
else:
|
||||
raise ValueError('Unexpected object type: {}'.format(ref['type']))
|
||||
|
||||
|
||||
def make_compound_id(ref):
|
||||
"""Returns the compound ID for the referenced object"""
|
||||
if ref['type'] == 'category':
|
||||
from indico_livesync.models.queue import EntryType
|
||||
if ref['type'] == EntryType.category:
|
||||
raise ValueError('Compound IDs are not supported for categories')
|
||||
elif ref['type'] == 'event':
|
||||
return ref['event_id']
|
||||
elif ref['type'] == 'contribution':
|
||||
return '{}.{}'.format(ref['event_id'], ref['contrib_id'])
|
||||
elif ref['type'] == 'subcontribution':
|
||||
return '{}.{}.{}'.format(ref['event_id'], ref['contrib_id'], ref['subcontrib_id'])
|
||||
else:
|
||||
raise ValueError('Unexpected object type: {}'.format(ref['type']))
|
||||
obj = obj_deref(ref)
|
||||
if isinstance(obj, Event):
|
||||
return unicode(obj.id)
|
||||
elif isinstance(obj, Contribution):
|
||||
return '{}.{}'.format(obj.event_id, obj.id)
|
||||
elif isinstance(obj, SubContribution):
|
||||
return '{}.{}.{}'.format(obj.contribution.event_id, obj.contribution_id, obj.id)
|
||||
raise ValueError('Unexpected object type: {}'.format(ref['type']))
|
||||
|
||||
|
||||
def clean_old_entries():
|
||||
@ -109,4 +107,9 @@ def get_excluded_categories():
|
||||
|
||||
|
||||
def is_ref_excluded(ref):
|
||||
return ref['category_id'] in get_excluded_categories()
|
||||
from indico_livesync.models.queue import EntryType
|
||||
if ref['type'] == EntryType.category:
|
||||
return ref['category_id'] in get_excluded_categories()
|
||||
else:
|
||||
obj = obj_deref(ref)
|
||||
return unicode(obj.event_new.category_id) in {unicode(x) for x in get_excluded_categories()}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user