from AccessControl import ClassSecurityInfo from DateTime import DateTime from DateTime.interfaces import DateTimeError from Products.ATContentTypes import ATCTMessageFactory as atct_mf from Products.ATContentTypes.content import folder from Products.ATContentTypes.lib.calendarsupport import CalendarSupportMixin from Products.ATReferenceBrowserWidget import ATReferenceBrowserWidget from Products.Archetypes import atapi from Products.CMFCore import permissions from Products.CMFCore.utils import getToolByName from Products.DataGridField import DataGridField from Products.DataGridField.SelectColumn import SelectColumn from ftw.calendarwidget.browser.widgets import FtwCalendarWidget from ftw.meeting import meetingMessageFactory as _ from ftw.meeting.config import PROJECTNAME from ftw.meeting.content.widget import DataGridWidgetExtended from ftw.meeting.interfaces import IMeeting from zope import component, schema from zope.interface import implements MeetingSchema = folder.ATFolderSchema.copy() + atapi.Schema(( atapi.DateTimeField( name='start_date', searchable=True, required=True, accessor='start', schemata='default', widget=FtwCalendarWidget( helper_js=('start_end_date_helper.js',), label=_(u"meeting_label_start_date", default=u"Start Date"), description=_(u"meeting_help_start_date", default=u"Enter the starting date and time, " "or click the calendar icon and select it.") )), atapi.DateTimeField( name='end_date', searchable=True, required=True, accessor='end', schemata='default', widget=FtwCalendarWidget( label=_( u"meeting_label_end_date", default=u"End Date"), description=_( u"meeting_help_end_date", default=u"Enter the ending date and time, " "or click the calendar icon and select it."))), atapi.StringField( name='meeting_type', searchable=False, schemata='default', required=True, default='event', vocabulary_factory='ftw.meeting.types', widget=atapi.SelectionWidget( label=_(u"meeting_label_type", default=u"Event type"), description=_(u"meeting_help_type", default=u"Choose your event type."), helper_js=['meeting_toggle_date.js', ], format='radio')), DataGridField( name='responsibility', searchable=False, schemata='default', columns=('contact', ), allow_empty_rows=False, widget=DataGridWidgetExtended( label=_(u"meeting_label_responsibility", default=u"Responsibility"), description=_( u"meeting_help_responsibility", default=u"Enter the responsible of the meeting."), auto_insert=True, select_all_column='contact', columns={ 'contact': SelectColumn( label=_( u"meeting_label_responsibility", default="Responsibility"), vocabulary='getAttendeesVocabulary' ), })), atapi.StringField( name='meeting_form', required=False, searchable=True, schemata='meeting', vocabulary='getMeetingForms', widget=atapi.SelectionWidget( label=_(u"meeting_label_meeting_form", default=u"Meeting Form"), description=_(u"meeting_help_meeting_form", default=u"Choose your Meeting form."), format='radio')), atapi.LinesField( name='head_of_meeting', required=False, searchable=True, schemata='meeting', index='KeywordIndex:schema', vocabulary_factory='ftw.meeting.users', widget=atapi.SelectionWidget( label=_(u"meeting_label_head_of_meeting", default=u"Head of Meeting"), description=_(u"meeting_help_head_of_meeting", default=u"Select the head of the meeting."))), atapi.LinesField( name='recording_secretary', required=False, searchable=True, schemata='meeting', index='KeywordIndex:schema', vocabulary_factory='ftw.meeting.users', widget=atapi.SelectionWidget( label=_(u"meeting_label_recording_secretary", default=u"Recording Secretary"), description=_(u"meeting_help_recording_secretary", default=u"Select the recording secretary."))), DataGridField( name='attendees', searchable=True, schemata='meeting', columns=('contact', 'present'), allow_empty_rows=False, widget=DataGridWidgetExtended( label=_(u"meeting_label_attendees", default=u"Attendees"), description=_(u"meeting_help_attendees", default=u"Enter the attendees of the meeting."), auto_insert=True, select_all_column='contact', columns={ 'contact': SelectColumn( label=_( u"meeting_label_attendees_attendee", default=u"Attendee"), vocabulary='getAttendeesVocabulary' ), 'present': SelectColumn( label=_( u"meeting_label_attendees_present", default=u"Present"), vocabulary='getPresentOptions', ), })), atapi.ReferenceField( name='related_items', relationship='relatesTo', multiValued=True, isMetadata=True, schemata='default', languageIndependent=False, widget=ATReferenceBrowserWidget.ReferenceBrowserWidget( allow_search=True, allow_browse=True, show_indexes=False, force_close_on_insert=False, label=_(u"meeting_label_related_items", default=u"Related Items"), description=_(u"meeting_help_related_items", default=u""), visible={'edit': 'visible', 'view': 'invisible'} )), )) MeetingSchema.changeSchemataForField('effectiveDate', 'settings') MeetingSchema.changeSchemataForField('expirationDate', 'settings') # use plone default location field MeetingSchema.moveField('location', after='end_date') MeetingSchema.moveField('description', after='end_date') MeetingSchema['location'].searchable = True MeetingSchema['location'].schemata = 'default' MeetingSchema['location'].widget = atapi.StringWidget( label=_(u"meeting_label_location", default=u"Location"), description=_( u"meeting_help_location", default=u"Enter the location where the meeting will take place."), ) MeetingSchema['location'].write_permission = permissions.ModifyPortalContent MeetingSchema.changeSchemataForField('effectiveDate', 'settings') MeetingSchema.changeSchemataForField('expirationDate', 'settings') MeetingSchema['effectiveDate'].widget.visible = {'view': 'invisible', 'edit': 'invisible'} MeetingSchema['expirationDate'].widget.visible = {'view': 'invisible', 'edit': 'invisible'} class Meeting(folder.ATFolder, CalendarSupportMixin): """A type for meetings.""" implements(IMeeting) security = ClassSecurityInfo() portal_type = "Meeting" schema = MeetingSchema # based on Products.ATContentTypes.content.event.ATEvent security.declareProtected(permissions.View, 'post_validate') def post_validate(self, REQUEST=None, errors=None): """Validates start and end date End date must be after start date """ if 'start_date' in errors or 'end_date' in errors: # No point in validating bad input return rstartDate = REQUEST.get('start_date', None) rendDate = REQUEST.get('end_date', None) if rendDate: try: end = DateTime(rendDate) except DateTimeError: errors['end_date'] = atct_mf(u'error_invalid_end_date', default=u'End date is not valid.') else: end = self.end() if rstartDate: try: start = DateTime(rstartDate) except DateTimeError: errors['start_date'] = _(u'error_invalid_start_date', default=u'Start date is not valid.') else: start = self.start() if 'start_date' in errors or 'end_date' in errors: # No point in validating bad input return if start > end: errors['end_date'] = atct_mf( u'error_end_must_be_after_start_date', default=u'End date must be after start date.') def getAttendeesVocabulary(self): """Workaround for DatagridField SelectColumn Because SelectColumn doesn't suppoert vocabulary_factory """ factory = component.getUtility( schema.interfaces.IVocabularyFactory, name='ftw.meeting.users', context=self) # converts the list of simpleterms into a displaylist # and resturns the result directly display_list = atapi.DisplayList() for term in factory(self): display_list.add(term.value, term.title or term.token) return display_list def getResponsibilityInfos(self, userids): """calls an utility, which returns a list of users format: {'fullname':'Demo User', 'url':'portal/author/userid'} """ result = [] if not userids: return elif isinstance(userids, list) or isinstance(userids, tuple): for userid in userids: result.append(self.getUserInfos(userid)) else: result.append(self.getUserInfos(userids)) return result @property def endDate(self): return self.end_date @property def startDate(self): return self.start_date def setStartDate(self, date): self.getField('start_date').set(self, date) def setEndDate(self, date): self.getField('end_date').set(self, date) def getUserInfos(self, userid): """ return a dict with userinformations, about the user """ mt = getToolByName(self, 'portal_membership') user = mt.getMemberById(userid) if user: fullname = user.getProperty('fullname', '') if not fullname: fullname = userid return {'name': fullname, 'url': '%s/author/%s' % ( self.portal_url(), user.id, )} else: catalog = getToolByName(self, 'portal_catalog') brains = catalog(dict(UID=userid)) if len(brains): brain = brains[0] return {'name': brain.Title, 'url': brain.getPath()} return {'name': userid, 'url': ''} def getPresentOptions(self): """returns present options """ return atapi.DisplayList( ( ('present', _(u'present')), ('absent', _(u'absent')), ('excused', _(u'excused')))) def getAttendeesOrUsers(self): def _get_usernames(data): names = [] for item in data: if item and isinstance(item, (str, unicode)): names.append(item) elif isinstance(item, dict): name = item.get('contact', None) if name: names.append(name) return names usernames = _get_usernames(self.getResponsibility()) if self.getMeeting_type() == 'meeting': usernames += _get_usernames(self.getHead_of_meeting()) usernames += _get_usernames(self.getRecording_secretary()) usernames += _get_usernames(self.getAttendees()) usernames = list(set(usernames)) return usernames def getMeetingTypes(self): """Returns a DisplayList of meeting types The ids are schemata names concatenated by _ """ return atapi.DisplayList(( ('dates_additional', _(u'meeting_type_event')), ('meeting_dates_additional', _(u'meeting_type_meeting')))) def getMeetingForms(self): """Returns a DisplayList of meeting forms from property """ values = atapi.DisplayList() m_props = self.portal_properties.get('ftw_meeting_properties') if m_props: for item in getattr(m_props, 'meeting_form'): values.add(item, item) return values.sortedByValue() #information for ical export def getEventType(self): return False def contact_name(self): return ','.join(self.getHead_of_meeting()) def contact_phone(self): return "" def contact_email(self): return "" def event_url(self): return self.absolute_url() @property def sortAttribute(self): return 'getObjPositionInParent' @property def sortOrder(self): return 'ascending' def sortable_responsibility(self): if self.getResponsibility(): return [r['contact'] for r in self.getResponsibility()] security.declarePublic('canSetDefaultPage') def canSetDefaultPage(self): return False atapi.registerType(Meeting, PROJECTNAME)