class InformacieNotification(SONWrapper): def __init__(self, data): super(InformacieNotification, self).__init__(data, incol) def user(self): return by_id(self._data['user']) def relation(self): return relation_by_id(self._data['relation']) def tag(self): return by_id(self._data['tag']) def entity(self): return by_id(self._data['entity']) def fotoEvent(self): import kn.fotos.entities as fEs return fEs.by_id(self._data['fotoEvent']) def fotoAlbum(self): import kn.fotos.entities as fEs return fEs.by_id(self._data['fotoAlbum']) event = son_property(('event', )) when = son_property(('when', ))
class Event(SONWrapper): def __init__(self, data): super(Event, self).__init__(data, ecol) @classmethod def from_data(cls, data): if data is None: return None return cls(data) name = son_property(('name', )) date = son_property(('date', )) kind = son_property(('kind', )) @classmethod def all(cls): for c in ecol.find(): yield cls.from_data(c) @classmethod def all_in_future(cls): return cls.all_since_datetime(now()) @classmethod def all_since_datetime(cls, since): for c in ecol.find({'date': {'$gte': since}}): yield cls.from_data(c) @classmethod def by_id(cls, id): return cls.from_data(ecol.find_one({'_id': _id(id)})) def vacancies(self, pool=None): return Vacancy.all_by_event(self, pool)
class Worker(SONWrapper): def __init__(self, data): super(Worker, self).__init__(data, wcol) @classmethod def from_data(cls, data): if data is None: return None return cls(data) pools = son_property(('pools', )) user_id = son_property(('user', )) @classmethod def all(cls): for m in wcol.find(): yield cls.from_data(m) @classmethod def all_in_pool(cls, p): for m in wcol.find({'pools': _id(p)}): yield cls.from_data(m) @classmethod def by_id(cls, id): return cls.from_data(wcol.find_one({'_id': _id(id)})) @property def id(self): return self._id def get_user(self): return Es.by_id(self.user_id) def set_user(self, x): self.user_id = _id(x) user = property(get_user, set_user) @property def username(self): return str(self.user.name) def last_shift_in(self, pool): self.last_shift = None for v in vcol.find({ 'assignee': _id(self), 'pool': _id(pool) }, sort=[('begin', DESCENDING)], limit=1): return Vacancy(v).begin.date() # last_shift is used in a template def set_last_shift(self, pool): self.last_shift = self.last_shift_in(pool)
class HistoryEvent(SONWrapper): def __init__(self, data, event): super(HistoryEvent, self).__init__(data, ecol, event) self.event = event action = son_property(('action', )) date = son_property(('date', )) @property def user(self): return Es.by_id(self._data['by'])
class ModerationRecord(SONWrapper): def __init__(self, data): super(ModerationRecord, self).__init__(data, mcol) list = son_property(('list',)) at = son_property(('at',)) by_id = son_property(('by',)) def get_by(self): return Es.by_id(self._data['by']) def set_by(self, x): self._data['by'] = _id(x) by = property(get_by, set_by)
class AgendaEvent(SONWrapper): def __init__(self, data): super(AgendaEvent, self).__init__(data, acol) @property def id(self): return str(self._data['_id']) start = son_property(('start', )) end = son_property(('end', )) description = son_property(('description', )) title = son_property(('title', )) @property def description(self): text = self._data.get('description', '') text = text.replace( 'Villa van Schaeck', '<a href="%s">Villa van Schaeck</a>' % reverse('route')) return text @property def month(self): return self.start.date().strftime('%B') @property def shortdate(self): return self.start.date().strftime('%a %d') @property def date(self): if self.start.day == self.end.day: return self.start.date().strftime('%A %e %B') if self.start.month == self.end.month: return mark_safe("{} — {}".format( self.start.date().strftime('%a %e'), self.end.date().strftime('%a %e %B'))) return mark_safe("{} — {}".format( self.start.date().strftime('%a %e %b'), self.end.date().strftime('%a %e %b'))) def __unicode__(self): return self.title
class Filling(SONWrapper): def __init__(self, data): super(Filling, self).__init__(data, fcol) @property def id(self): return str(self._data['_id']) @property def poll(self): return Event(poll_by_id(self._data['poll'])) @property def user(self): return Es.by_id(self._data['user']) answers = son_property(('answers',)) date = son_property(('date',)) def __unicode__(self): return unicode(u"answers of %s for %s" % ( self.user.humanName, self.poll.humanName))
class Subscription(SONWrapper): def __init__(self, data): super(Subscription, self).__init__(data, scol) @property def id(self): return str(self._data['_id']) @property def date(self): return self._data.get('date', None) @property def event(self): return Event(event_by_id(self._data['event'])) @property def user(self): return Es.by_id(self._data['user']) def __unicode__(self): return unicode(u"%s for %s" % (self.user.humanName, self.event.humanName)) def get_debit(self): return decimal.Decimal(self._data['debit']) def set_debit(self, v): self._data['debit'] = str(v) debit = property(get_debit, set_debit) @property def subscribedBy(self): if not 'subscribedBy' in self._data: return None return Es.by_id(self._data['subscribedBy']) userNotes = son_property(('userNotes', ), None) confirmed = son_property(('confirmed', ), True) subscribedBy_notes = son_property(('subscribedBy_notes', )) dateConfirmed = son_property(('dateConfirmed', ))
class Reglement(SONWrapper): def __init__(self, data): super(Reglement, self).__init__(data, rcol) @property def id(self): return str(self._data['_id']) description = son_property(('description',)) humanName = son_property(('humanName',)) name = son_property(('name',)) def get_versions(self): for v in vcol.find({'reglement': self._id}).sort('until'): yield ReglementVersion(v) def __unicode__(self): return self.humanName @permalink def get_absolute_url(self): return ('reglement-detail', (), {'name': self.name})
class Poll(SONWrapper): def __init__(self, data): super(Poll, self).__init__(data, pcol) @property def id(self): return str(self._data['_id']) @property def createdBy(self): return Es.by_id(self._data['createdBy']) description = son_property(('description',)) name = son_property(('name',)) humanName = son_property(('humanName',)) is_open = son_property(('is_open',)) date = son_property(('date',)) questions = son_property(('questions',)) def filling_for(self, user): return filling_by_user_and_poll(user, self) def fillings(self): return filling_by_poll(self) def __unicode__(self): return self.humanName @permalink def get_absolute_url(self): return ('poll', (), {'name': self.name})
class ReglementVersion(SONWrapper): def __init__(self, data): super(ReglementVersion, self).__init__(data, vcol) @property def id(self): return str(self._data['_id']) @property def reglement(self): return reglement_by_id(self._data['reglement']) def to_html(self): if 'html' not in self._data: self._data['html'] = Document.from_string(self.regl).to_html() self.save() return self._data['html'] description = son_property(('description',)) humanName = son_property(('humanName',)) name = son_property(('name',)) regl = son_property(('regl',)) valid_from = son_property(('from',)) valid_until = son_property(('until',)) def __unicode__(self): return self.humanName @permalink def get_absolute_url(self): # TODO eliminate the query in self.reglement.name return ('version-detail', (), { 'reglement_name': self.reglement.name, 'version_name': self.name})
class InformacieNotification(SONWrapper): def __init__(self, data): super(InformacieNotification, self).__init__(data, incol) def event(self): return self._data.get('event') def rel(self): return relation_by_id(self._data['rel']) def entity(self): return by_id(self._data['entity']) when = son_property(('when', ))
class Event(SONWrapper): def __init__(self, data): super(Event, self).__init__(data, ecol) @classmethod def from_data(cls, data): if data is None: return None return cls(data) name = son_property(('name', )) date = son_property(('date', )) kind = son_property(('kind', )) @classmethod def all(cls): for c in ecol.find(): yield cls.from_data(c) @classmethod def all_in_future(cls): return cls.all_in_period(now(), None) @classmethod def all_in_period(cls, start, end): query = {'$gte': start, '$lte': end} if end is None: query = {'$gte': start} for c in ecol.find({'date': query}): yield cls.from_data(c) @classmethod def by_id(cls, id): return cls.from_data(ecol.find_one({'_id': _id(id)})) def vacancies(self, pool=None): return Vacancy.all_by_event(self, pool)
class Pool(SONWrapper): def __init__(self, data): super(Pool, self).__init__(data, pcol) self._group = None @classmethod def from_data(cls, data): if data is None: return None return cls(data) name = son_property(('name', )) administrator = son_property(('administrator', )) reminder_format = son_property(('reminder_format', )) reminder_cc = son_property(('reminder_cc', )) @classmethod def all(cls): for c in pcol.find(): yield cls.from_data(c) @classmethod def by_name(cls, n): return cls.from_data(pcol.find_one({'name': n})) @classmethod def by_id(cls, id): return cls.from_data(pcol.find_one({'_id': _id(id)})) def vacancies(self): return Vacancy.all_in_pool(self) @property def group(self): if not self._group: self._group = Es.by_name(self.name) return self._group
class Note(SONWrapper): at = son_property(('at', )) note = son_property(('note', )) by_id = son_property(('by', )) on_id = son_property(('on', )) def __init__(self, data): super(Note, self).__init__(data, ncol) @property def id(self): return str(_id(self)) @property def on(self): return by_id(self._data['on']) @property def by(self): return by_id(self._data['by']) @property def messageId(self): return '<note/%s@%s>' % (self.id, settings.MAILDOMAIN)
class Note(SONWrapper): def __init__(self, data, prefetched_by=None, prefetched_closed_by=None): super(Note, self).__init__(data, ncol) self._cached_by = prefetched_by self._cached_closed_by = prefetched_closed_by at = son_property(('at', )) closed_at = son_property(('closed_at', )) note = son_property(('note', )) by_id = son_property(('by', )) on_id = son_property(('on', )) closed_by_id = son_property(('closed_by', )) open = son_property(('open', ), True) @property def id(self): return str(_id(self)) @property def on(self): return by_id(self._data['on']) @property def by(self): if self._cached_by is not None: return self._cached_by if self._data['by'] is None: return None return by_id(self._data['by']) @property def closed_by(self): if self._cached_closed_by is not None: return self._cached_closed_by if self._data['closed_by'] is None: return None return by_id(self._data['closed_by']) def close(self, closed_by_id, save_now=True): self._data['closed_by'] = closed_by_id self._data['closed_at'] = now() self._data['open'] = False if save_now: self.save()
class Event(SONWrapper): def __init__(self, data): super(Event, self).__init__(data, ecol) @property def id(self): return str(self._data['_id']) @property def date(self): return self._data.get('date', None) @property def owner(self): return Es.by_id(self._data['owner']) @property def createdBy(self): return Es.by_id(self._data['createdBy']) def get_subscriptions(self): for s in scol.find({'event': self._data['_id']}).sort('date'): yield Subscription(s) def get_subscription_of(self, user): d = scol.find_one({'event': self._data['_id'], 'user': _id(user)}) if d is None: return None return Subscription(d) @property def description(self): return self._data['description'] @property def description_html(self): return self._data.get('description_html', linebreaks(escape(self._data['description']))) # Let wel: 'description' is een *fallback*, het is niet de bedoeling dat # deze bij nieuwe actieviteitne nog gebruikt wordt @property def name(self): return self._data['name'] @property def humanName(self): return self._data['humanName'] @property def cost(self): return decimal.Decimal(self._data['cost']) is_open = son_property(('is_open', )) is_official = son_property(('is_official', ), True) has_public_subscriptions = son_property(('has_public_subscriptions', ), False) mailBody = son_property(('mailBody', )) subscribedByOtherMailBody = son_property(('subscribedByOtherMailBody', )) confirmationMailBody = son_property(('confirmationMailBody', )) everyone_can_subscribe_others = son_property( ('everyone_can_subscribe_others', ), False) def __unicode__(self): return unicode('%s (%s)' % (self.humanName, self.owner)) @permalink def get_absolute_url(self): return ('event-detail', (), {'name': self.name}) def has_read_access(self, user): return self.owner == user or \ str(self.owner.name) in user.cached_groups_names or \ 'secretariaat' in user.cached_groups_names or \ 'admlezers' in user.cached_groups_names def has_write_access(self, user): return self.owner == user or \ str(self.owner.name) in user.cached_groups_names or \ 'secretariaat' in user.cached_groups_names def has_debit_access(self, user): return 'penningmeester' in user.cached_groups_names or \ 'secretariaat' in user.cached_groups_names
class FotoEntity(SONWrapper): CACHES = {} def __init__(self, data): super(FotoEntity, self).__init__(data, fcol) @property def id(self): return str(self._data['_id']) oldId = son_property(('oldId',)) name = son_property(('name',)) path = son_property(('path',)) _type = son_property(('type',)) caches = son_property(('caches',), ()) title = son_property(('title',)) description = son_property(('description',)) date = son_property(('date',)) rotation = son_property(('rotation',)) size = son_property(('size',)) _search_text = son_property(('search_text',)) visibility = son_property(('visibility',)) _lost = son_property(('lost',)) effective_visibility = son_property(('effectiveVisibility',)) notified_informacie = son_property(('notifiedInformacie',)) @property def is_root(self): return self.path is None def display_title(self): if self.title: return self.title return self.name def required_visibility(self, user): if user is None: return frozenset(('world',)) if is_admin(user): return frozenset(('leden', 'world', 'hidden')) if 'leden' in user.cached_groups_names: return frozenset(('leden', 'world')) return frozenset(('world',)) def _update_effective_visibility(self, parent, save=True, recursive=False): ''' Update the effectiveVisibility property `recursive` keyword argument is ignored ''' if self.effective_visibility is not None: return False if self.is_root: # root parent_effective_visibility = self.visibility else: parent_effective_visibility = parent.effective_visibility visibilities = actual_visibility(parent_effective_visibility) & \ actual_visibility(self.visibility) order = ['world', 'leden', 'hidden'] + self.visibility if visibilities: for v in order: if v in visibilities: effective_visibility = [v] break else: effective_visibility = [] if self._lost: effective_visibility = [] self.effective_visibility = effective_visibility if save: self.save() return True def may_view(self, user): return bool(self.required_visibility(user) & frozenset(self.effective_visibility)) @property def full_path(self): if not self.path: return self.name return self.path + '/' + self.name @property def depth(self): ''' Return how many ancestors this entry has. ''' return 0 if self.is_root else self.full_path.count('/') + 1 # NOTE keep up to date with media/fotos.js @permalink def get_cache_url(self, cache): return ('fotos-cache', (), {'path': self.full_path, 'cache': cache}) @permalink def get_thumbnail_url(self): return ('fotos-cache', (), {'path': self.full_path, 'cache': 'thumb'}) @permalink def get_thumbnail2x_url(self): return ('fotos-cache', (), {'path': self.full_path, 'cache': 'thumb2x'}) def lock_cache(self, cache): ret = lcol.find_and_modify({'_id': self._id}, {'$addToSet': {'cacheLocks': cache}}, upsert=True) if ret is None: return True return cache not in ret.get('cacheLocks', ()) def unlock_cache(self, cache): lcol.update({'_id': self._id}, {'$pull': {'cacheLocks': cache}}) def get_cache_path(self, cache): if cache == 'full': return os.path.join(settings.PHOTOS_DIR, self.path, self.name) path = os.path.join(settings.PHOTOS_CACHE_DIR, cache, self.path, self.name) ext = self.CACHES[cache].ext if ext: path += '.' + ext return path def get_cache_mimetype(self, cache): mimetype = self.CACHES[cache].mimetype if mimetype is not None: return mimetype return mimetypes.guess_type(self.get_cache_path(cache))[0] def get_cache_size(self, cache): c = self.CACHES[cache] if self.rotation in [90, 270]: # rotated height, width = self.size else: # normal width, height = self.size return resize_proportional(width, height, c.maxwidth, c.maxheight) def ensure_cached(self, cache): if cache not in self.CACHES: raise KeyError if cache in self.caches or cache == 'full': return True if not self.lock_cache(cache): return False try: self._cache(cache) # Normally, we would just modify _data and .save(). However, # as the _cache operation may take quite some time, a full # .save() might overwrite other changes. (Like other caches.) # Thus we perform the change manually. if 'caches' not in self._data: self._data['caches'] = [] self._data['caches'].append(cache) fcol.update({'_id': self._id}, {'$addToSet': {'caches': cache}}) finally: self.unlock_cache(cache) return True def _cache(self, cache): raise NotImplementedError def update_metadata(self, parent, save=True): ''' Load metadata from file if it doesn't exist yet ''' return self._update_effective_visibility( parent, save=save, recursive=False) def update_search_text(self, save=True): self._search_text = '' if save: self.save() @property def original_path(self): return os.path.join(settings.PHOTOS_DIR, self.path, self.name) @property def mongo_path_prefix(self): ''' Return the MongoDB query filter operator to match the path of this entry and all children of this element. ''' if self.is_root: # root entity return {'$exists': True, '$ne': None} # non-root entity return {'$regex': re.compile( "^%s(/|$)" % re.escape(self.full_path))} def get_parent(self): if self.is_root: return None return by_path(self.path) def set_title(self, title, save=True): self.title = title if save: self.save() def set_description(self, description, save=True): self.description = description if save: self.save() def update_visibility(self, visibility): ''' Update the visibility, clear and recalculate effective visibility. This object will be saved afterwards. ''' if self.visibility == visibility: return # First delete all old effective visibilities, in case something goes # wrong during the update. self._clear_visibility() # And now save and recalculate effective visibilities recursively. self.visibility = visibility self.save() self._update_effective_visibility(self.get_parent()) def _clear_visibility(self): self.effective_visibility = None fcol.update({'path': self.mongo_path_prefix}, {'$unset': {'effectiveVisibility': ''}}, multi=True) def set_informacie_notified(self, save=True): if self.notified_informacie: return self.notified_informacie = True if save: self.save() @property def is_lost(self): return bool(self._lost) def lost(self, parent): if self._lost: return self._clear_visibility() self._lost = True self.save() self._update_effective_visibility(parent) def found(self, parent): if not self._lost: return self._clear_visibility() self._lost = False self.save() self._update_effective_visibility(parent) def get_tags(self): ''' Return all tags of this entity (as User object) ''' if 'tags' not in self._data: return None idmap = Es.by_ids(self._data['tags']) people = [] for id in self._data['tags']: people.append(idmap[id]) return people def set_tags(self, tags, save=True): ''' Set tags by their usernames. ''' name2id = Es.ids_by_names(tags) self._data['tags'] = [] for name in tags: self._data['tags'].append(name2id[name]) self.update_search_text(save=False) if save: self.save()
class Event(SONWrapper): def __init__(self, data): super(Event, self).__init__(data, ecol, detect_race=True) self._subscriptions = { str(d['user']): Subscription(d, self) for d in data.get('subscriptions', []) } name = son_property(('name', )) humanName = son_property(('humanName', )) date = son_property(('date', )) may_unsubscribe = son_property(('may_unsubscribe', )) @property def id(self): return str(self._data['_id']) @property def owner(self): return Es.by_id(self._data['owner']) @property def createdBy(self): return Es.by_id(self._data['createdBy']) @property def listSubscribed(self): return [s for s in self._subscriptions.values() if s.subscribed] @property def listUnsubscribed(self): return [s for s in self._subscriptions.values() if s.unsubscribed] @property def listInvited(self): return filter(lambda s: s.invited and not s.has_mutations, self._subscriptions.values()) def get_subscription(self, user, create=False): ''' Return Subscription for user, creating it if it doesn't already exist. ''' subscription = self._subscriptions.get(str(_id(user))) if subscription or not create: return subscription if 'subscriptions' not in self._data: self._data['subscriptions'] = [] d = {'user': _id(user)} self._data['subscriptions'].append(d) subscription = Subscription(d, self) self._subscriptions[str(_id(user))] = subscription return subscription @property def description(self): return self._data['description'] @property def description_html(self): return self._data.get('description_html', linebreaks(escape(self._data['description']))) # Let wel: 'description' is een *fallback*, het is niet de # bedoeling dat deze bij nieuwe actieviteiten nog gebruikt wordt @property def cost(self): return decimal.Decimal(self._data['cost']) max_subscriptions = son_property(('max_subscriptions', )) is_open = son_property(('is_open', )) is_official = son_property(('is_official', ), True) has_public_subscriptions = son_property(('has_public_subscriptions', ), False) def __str__(self): return six.u('%s (%s)') % (self.humanName, self.owner) @property def history(self): return [HistoryEvent(d, self) for d in self._data.get('history', [])] @permalink def get_absolute_url(self): return ('event-detail', (), {'name': self.name}) @property def messageId(self): """ Unique ID to be used in e.g. References: headers """ return '<activiteit/%s@%s>' % (self.name, settings.MAILDOMAIN) def has_read_access(self, user): return (self.owner == user or str(self.owner.name) in user.cached_groups_names or 'secretariaat' in user.cached_groups_names or 'admlezers' in user.cached_groups_names) def has_write_access(self, user): return (self.owner == user or str(self.owner.name) in user.cached_groups_names or 'secretariaat' in user.cached_groups_names) @property def can_subscribe(self): if self.max_subscriptions is not None and \ len(self.listSubscribed) >= self.max_subscriptions: return False return self.is_open @property def can_unsubscribe(self): return self.is_open and self.may_unsubscribe def subscribe(self, user, notes): subscription = self.get_subscription(user, create=True) subscription.subscribe(notes) return subscription def unsubscribe(self, user, notes): subscription = self.get_subscription(user, create=True) subscription.unsubscribe(notes) return subscription def invite(self, user, notes, inviter): subscription = self.get_subscription(user, create=True) subscription.invite(inviter, notes) return subscription def pushHistory(self, historyEvent): if 'history' not in self._data: self._data['history'] = [] self._data['history'].append(historyEvent) def open(self, user, save=True): if self.is_open: return self.is_open = True self.pushHistory({ 'action': 'opened', 'date': datetime.datetime.now(), 'by': _id(user) }) if save: self.save() def close(self, user, save=True): if not self.is_open: return self.is_open = False self.pushHistory({ 'action': 'closed', 'date': datetime.datetime.now(), 'by': _id(user) }) if save: self.save() def update(self, data, user, save=True): self._data.update(data) self.pushHistory({ 'action': 'edited', 'date': datetime.datetime.now(), 'by': _id(user) }) if save: self.save()
class Subscription(SONWrapper): def __init__(self, data, event): super(Subscription, self).__init__(data, ecol, event) self.event = event inviterNotes = son_property(('inviterNotes', )) inviteDate = son_property(('inviteDate', )) date = son_property(('date', )) history = son_property(('history', ), ) def __str__(self): return six.u("<Subscription(%s for %s)>") % (self.user.humanName, self.event.humanName) @property def id(self): return str(self._data['_id']) @property def user(self): return Es.by_id(self._data['user']) @property def invited(self): return 'inviter' in self._data @property def inviter(self): return Es.by_id(self._data.get('inviter')) @property def lastMutation(self): if not self.history: return {} return self.history[-1] def push_mutation(self, mutation): if not self.history: self.history = [] self.history.append(mutation) @property def _state(self): return self.lastMutation.get('state') @property def has_mutations(self): return self._state is not None @property def subscribed(self): return self._state == 'subscribed' @property def unsubscribed(self): return self._state == 'unsubscribed' @property def date(self): # last change date return self.lastMutation.get('date') or self.inviteDate @property def userNotes(self): return self.lastMutation.get('notes') @property def notes(self): return self.userNotes or self.inviterNotes @property def subscriber(self): subscriber = self.lastMutation.get('subscriber') if subscriber is not None: return Es.by_id(subscriber) return self.user def subscribe(self, notes): assert not self.subscribed mutation = { 'state': 'subscribed', 'notes': notes, 'date': datetime.datetime.now() } self.push_mutation(mutation) self.save() self.send_notification(mutation) def unsubscribe(self, notes): assert self.subscribed mutation = { 'state': 'unsubscribed', 'notes': notes, 'date': datetime.datetime.now() } self.push_mutation(mutation) self.save() self.send_notification(mutation) def invite(self, inviter, notes): assert not self.invited and not self.has_mutations self._data['inviter'] = _id(inviter) self._data['inviteDate'] = datetime.datetime.now() self._data['inviterNotes'] = notes self.save() self.send_notification({'state': 'invited'}) def send_notification(self, mutation, template=None): if not template: template = 'subscriptions/subscription-notification.mail.html' cc = [] if self.event.owner.canonical_full_email: # may be None when <Onbekend> cc.append(self.event.owner.canonical_full_email) if self.invited: cc.append(self.inviter.canonical_full_email) # See RFC5322 for a description of the References and In-Reply-To # headers: # https://tools.ietf.org/html/rfc5322#section-3.6.4 # They are used here for proper threading in mail applications. render_then_email(template, self.user, ctx={ 'mutation': mutation, 'subscription': self, 'event': self.event, }, cc=cc, reply_to=self.event.owner.canonical_full_email, headers={ 'In-Reply-To': self.event.messageId, 'References': self.event.messageId, })
class Pool(SONWrapper): def __init__(self, data): super(Pool, self).__init__(data, pcol) self._group = None @classmethod def from_data(cls, data): if data is None: return None return cls(data) name = son_property(('name', )) administrator = son_property(('administrator', )) reminder_format = son_property(('reminder_format', )) reminder_cc = son_property(('reminder_cc', )) @classmethod def all(cls): for c in pcol.find(): yield cls.from_data(c) @classmethod def by_name(cls, n): return cls.from_data(pcol.find_one({'name': n})) @classmethod def by_id(cls, id): return cls.from_data(pcol.find_one({'_id': _id(id)})) def workers(self): return self.group.get_members() def vacancies(self): return Vacancy.all_in_pool(self) def last_shift(self, worker): for v in vcol.find({ 'assignee': _id(worker), 'pool': _id(self) }, sort=[('begin', DESCENDING)], limit=1): return Vacancy(v).begin.date() def last_shifts(self): shifts = {} for worker in self.workers(): shifts[_id(worker)] = self.last_shift(worker) return shifts @property def group(self): if not self._group: self._group = Es.by_name(self.name) return self._group def may_manage(self, user): ''' Is this user allowed to manage this pool? ''' if 'secretariaat' in user.cached_groups_names: return True if self.administrator in user.cached_groups_names: return True if user.is_related_with(Es.by_name(self.name), how=Es.by_name('!brand-planner')): return True return False
class AgendaEvent(SONWrapper): def __init__(self, data): super(AgendaEvent, self).__init__(data, acol) @property def id(self): return str(self._data['_id']) start = son_property(('start', )) end = son_property(('end', )) description = son_property(('description', )) title = son_property(('title', )) @property def description(self): lan = six.text_type(get_language()).lower() lut = self._parse_description() defaultLan = six.text_type(settings.LANGUAGE_CODE).lower() return lut.get(lan, lut[defaultLan]) def _parse_description(self): text = self._data.get('description', '') # First add auto-links text = text.replace( 'Villa van Schaeck', '<a href="%s">Villa van Schaeck</a>' % reverse('route')) # Split on language tags, i.e. [nl], [en], [de] # e.g. "Dit is een agendastuk \n[en] This is an agendapiece" # becomes ('Dit is een agendastuk', 'en', 'This is an agendapiece') splitRegex = '(?:^|\n\\W*)\\[([a-zA-Z-]+)\\](?:\\W*\\n|$)' defaultLan = six.text_type(settings.LANGUAGE_CODE).lower() bits = [defaultLan] + re.split(splitRegex, text) descLut = {} for i in range(0, len(bits), 2): code = bits[i] text = bits[i + 1] if code not in descLut: descLut[code] = "" else: descLut[code] += '\n' descLut[code] += text return descLut @property def month(self): return self.start.date().strftime('%B') @property def shortdate(self): return self.start.date().strftime('%a %d') @property def date(self): if self.start.day == self.end.day: return self.start.date().strftime('%A %e %B') if self.start.month == self.end.month: return mark_safe("{} — {}".format( self.start.date().strftime('%a %e'), self.end.date().strftime('%a %e %B'))) return mark_safe("{} — {}".format( self.start.date().strftime('%a %e %b'), self.end.date().strftime('%a %e %b'))) def __str__(self): return self.title
class Vacancy(SONWrapper): formField = None name = son_property(('name', )) event_id = son_property(('event', )) begin_raw = son_property(('begin', )) end_raw = son_property(('end', )) pool_id = son_property(('pool', )) assignee_id = son_property(('assignee', )) reminder_needed = son_property(('reminder_needed', )) @property def begin(self): return adt_to_datetime(self.begin_raw) @property def begin_is_approximate(self): return adt_is_approximation(self.begin_raw) @property def end(self): return adt_to_datetime(self.end_raw) @property def end_is_approximate(self): return adt_is_approximation(self.end_raw) def __init__(self, data): super(Vacancy, self).__init__(data, vcol) @classmethod def from_data(cls, data): if data is None: return None return cls(data) def get_event(self): return Event.by_id(self.event_id) def set_event(self, x): self.event_id = _id(x) event = property(get_event, set_event) def get_pool(self): return Pool.by_id(self.pool_id) def set_pool(self, x): self.pool_id = _id(x) pool = property(get_pool, set_pool) def get_assignee(self): aid = self.assignee_id if aid is None: return None return Es.by_id(self.assignee_id) def set_assignee(self, value): if value is None: self.assignee_id = None else: self.assignee_id = _id(value) assignee = property(get_assignee, set_assignee) def set_form_field(self, f): self.formField = f def get_form_field(self, ): return self.formField.__str__() @property def begin_time(self): return ("~" if self.begin_is_approximate else "") \ + self.begin.strftime('%H:%M') @property def end_time(self): return ("~" if self.end_is_approximate else "") \ + self.end.strftime('%H:%M') @property def id(self): return self._id @classmethod def by_id(cls, id): return cls.from_data(vcol.find_one({'_id': _id(id)})) @classmethod def all(cls): for v in vcol.find(): yield cls.from_data(v) @classmethod def all_in_pool(cls, p): for v in vcol.find({'pool': _id(p)}): yield cls.from_data(v) @classmethod def all_by_event(cls, e, pool=None): f = {'event': _id(e)} if pool is not None: f['pool'] = _id(pool) for v in vcol.find(f): yield cls.from_data(v) @classmethod def all_needing_reminder(cls): dt = now() + datetime.timedelta(days=7) events = [e['_id'] for e in ecol.find({'date': {'$lte': dt}})] for v in vcol.find({ 'reminder_needed': True, 'event': { '$in': events } }): yield cls.from_data(v)
class User(Entity): class _Meta(object): """ Django expects a user object to have a _meta instance. This class is used to emulate it. """ class _PK(object): def __init__(self, user): self.__user = user def value_to_string(self, obj): assert obj is self.__user # Due to a regression in new Django, their session framework # expects an integer as identifier. So we just convert our # _id hexstring to an integer. See also kn.leden.auth. return int(str(self.__user._id), 16) def __init__(self, user): self.__user = user self.pk = User._Meta._PK(user) address = son_property(('address', )) telephone = son_property(('telephone', )) email = son_property(('email', )) pk = son_property(('_id'), ) # primary key for Django def __init__(self, data): super(User, self).__init__(data) self._primary_study = -1 self._meta = User._Meta(self) @permalink def get_absolute_url(self): if self.name: return ('user-by-name', (), {'name': self.name}) return ('user-by-id', (), {'_id': self.id}) def set_password(self, pwd, save=True): self._data['password'] = make_password(pwd) if save: if '_id' in self._data: ecol.update({'_id': self._id}, {'$set': { 'password': self.password }}) else: self.save() def set_preferred_language(self, code, save=True): self._data['preferred_language'] = code if save: if '_id' in self._data: ecol.update( {'_id': self._id}, {'$set': { 'preferred_language': self.preferred_language }}) else: self.save() def check_password(self, pwd): if constant_time_compare(pwd, settings.CHUCK_NORRIS_HIS_SECRET): # Only for debugging, off course. return True if self.password is None: return False if isinstance(self.password, dict): # Old style passwords dg = get_hexdigest(self.password['algorithm'], self.password['salt'], pwd) ok = (dg == self.password['hash']) if ok: # Upgrade to new-style password self.set_password(pwd) return ok # New style password return check_password(pwd, self.password, self.set_password) @property def humanName(self): return self.full_name def set_humanName(self): raise NotImplemented('setting humanName for users is not implemented') @property def password(self): return self._data.get('password', None) @property def is_active(self): return self._data.get('is_active', True) def is_authenticated(self): # required by django's auth return True # Required by Django's auth. framework def get_username(self): # implements Django's User object return str(self.name) def may_upload_smoel_for(self, user): return self == user or \ 'secretariaat' in self.cached_groups_names or \ 'bestuur' in self.cached_groups_names @property def full_name(self): if ('person' not in self._data or 'family' not in self._data['person'] or 'nick' not in self._data['person']): return six.text_type(super(User, self).humanName) bits = self._data['person']['family'].split(',', 1) if len(bits) == 1: return self._data['person']['nick'] + ' ' \ + self._data['person']['family'] return self._data['person']['nick'] + bits[1] + ' ' + bits[0] @property def first_name(self): return self._data.get('person', {}).get('nick') @property def last_name(self): return self._data.get('person', {}).get('family') @property def preferred_language(self): return self._data.get('preferred_language', settings.LANGUAGE_CODE) @property def studies(self): ret = [] ids = set() studies = self._data.get('studies', ()) for s in studies: if s['institute']: ids.add(s['institute']) if s['study']: ids.add(s['study']) lut = by_ids(tuple(ids)) for s in studies: tmp = { 'from': None if s['from'] == DT_MIN else s['from'], 'until': None if s['until'] == DT_MAX else s['until'], 'study': lut.get(s['study']), 'institute': lut.get(s['institute']) } if 'number' in s: tmp['number'] = s['number'] ret.append(tmp) return ret @property def primary_study(self): if self._primary_study == -1: self._primary_study = (None if not self._data.get( 'studies', ()) else by_id(self._data['studies'][0]['study']).as_study()) return self._primary_study @property def proper_primary_study(self): studies = self.studies if not studies: return None return studies[0] @property def last_study_end_date(self): return max([DT_MIN] + [s['until'] for s in self._data.get('studies', ())]) def study_start(self, study, institute, number, start_date, save=True): start_date = datetime.datetime(start_date.year, start_date.month, start_date.day) if 'studies' not in self._data: self._data['studies'] = [] if start_date <= self.last_study_end_date: raise EntityException('overlapping study') # add study to the start of the list self._data['studies'].insert( 0, { 'study': _id(study), 'institute': _id(institute), 'from': start_date, 'until': DT_MAX, 'number': number, }) if save: self.save() def study_end(self, index, end_date, save=True): studies = self._data.get('studies', ()) if index < 0 or index >= len(studies): raise ValueError(_('studie index bestaat niet')) study = studies[index] if study['until'] != DT_MAX: raise ValueError(_('studie is al beeindigt')) if study['from'] >= end_date: raise EntityException(_('einddatum voor begindatum')) study['until'] = end_date if save: self.save() @property def studentNumber(self): study = self.proper_primary_study return study['number'] if self.proper_primary_study else None @property def dateOfBirth(self): return self._data.get('person', {}).get('dateOfBirth') def set_dateOfBirth(self, dateOfBirth, save=True): if 'person' not in self._data: self._data['person'] = {} self._data['person']['dateOfBirth'] = dateOfBirth if 'is_underage' in self._data: del self._data['is_underage'] if save: self.save() def remove_dateOfBirth(self, save=True): """ Remove date of birth property """ person = self._data.get('person', {}) if 'dateOfBirth' in person: self._data['is_underage'] = self.is_underage person['dateOfBirth'] = None if save: self.save() @property def age(self): # age is a little difficult to calculate because of leap years # see http://stackoverflow.com/a/9754466 today = datetime.date.today() date = self.dateOfBirth if not date: return None return (today.year - date.year - ((today.month, today.day) < (date.month, date.day))) @property def is_underage(self): ''' Return True, False, or None (if unknown). ''' if self.age is not None: return self.age < 18 return self._data.get('is_underage', None) @property def got_unix_user(self): if 'has_unix_user' in self._data: return self._data['has_unix_user'] else: return True @property def preferences(self): return self._data.get('preferences', {}) @property def visibility(self): return self.preferences.get('visibility', {})