def planning_view(request): period = 'now' if request.GET.get('year') in {'past1', 'past2'}: period = request.GET['year'] pools = list(Pool.all()) poolid2index = dict() poolids = set() for pool in pools: poolids.add(_id(pool)) # TODO reduce number of queries current = date_to_midnight(now() - datetime.timedelta(days=1)) past1year = date_to_midnight(now() - datetime.timedelta(days=356)) past2year = date_to_midnight(now() - datetime.timedelta(days=356*2)) if period == 'now': start = current end = None elif period == 'past1': start = past1year end = current elif period == 'past2': start = past2year end = past1year event_entities = list(Event.all_in_period(start, end)) used_pools = set() for e in event_entities: for v in e.vacancies(): used_pools.add(v.pool_id) pools_tpl = [] i = 0 for pool in pools: if _id(pool) not in used_pools: continue poolid2index[pool._id] = i pools_tpl.append(pool) i += 1 events = list() for e in event_entities: ei = {'id': _id(e), 'name': e.name, 'datetime': e.date, 'kind': e.kind, 'vacancies': dict()} for index in poolid2index.values(): ei['vacancies'][index] = list() for v in e.vacancies(): ei['vacancies'][poolid2index[v.pool_id]].append({ 'begin': v.begin, 'begin_time': v.begin_time, 'end_time': v.end_time, 'assignee': v.assignee.humanName if v.assignee else "?"}) for index in poolid2index.values(): ei['vacancies'][index].sort(key=lambda x: x['begin']) events.append(ei) events.sort(key=lambda x: x['datetime']) return render_to_response('planning/overview.html', {'events': events, 'pools': pools_tpl, 'period': period}, context_instance=RequestContext(request))
def generate_postfix_map(giedo): tbl = dict() # the virtual map non_mailman_groups = {} dt_now = now() id2email = {} # handle the straightforward cases for e in Es.all(): if e.canonical_email is None or e.name is None: continue id2email[e._id] = e.canonical_email for nm in e.other_names: tbl["%s@%s" % (nm, MAILDOMAIN)] = (e.canonical_email,) if e.type == 'user': tbl[e.canonical_email] = (e.primary_email,) elif e.type == 'group': if e.got_mailman_list: tbl[e.canonical_email] = ('%s@%s' % ( str(e.name), LISTS_MAILDOMAIN),) else: tbl[e.canonical_email] = [] non_mailman_groups[e._id] = e else: logging.warn("postfix: unhandled type: %s" % e.type) # handle the non-mailman groups for rel in Es.query_relations(_with=non_mailman_groups.keys(), _from=dt_now, until=dt_now, how=None): e = non_mailman_groups[rel['with']] email = id2email.get(rel['who']) if email is not None: tbl[e.canonical_email].append(email) return tbl
def generate_postfix_map(giedo): tbl = dict() # the virtual map non_mailman_groups = {} dt_now = now() id2email = {} # handle the straightforward cases for e in Es.all(): if e.canonical_email is None: continue id2email[e._id] = e.canonical_email for nm in e.other_names: tbl["%s@%s" % (nm, settings.MAILDOMAIN)] = (e.canonical_email,) if e.type == 'user': tbl[e.canonical_email] = (e.email,) elif e.type == 'group': if e.got_mailman_list and e.name: tbl[e.canonical_email] = ('%s@%s' % ( str(e.name), settings.LISTS_MAILDOMAIN),) else: tbl[e.canonical_email] = [] non_mailman_groups[e._id] = e else: logging.warn("postfix: unhandled type: %s" % e.type) id_email = "%s@%s" % (e.id, settings.MAILDOMAIN) if id_email not in tbl: tbl[id_email] = (e.canonical_email,) # handle the non-mailman groups for rel in Es.query_relations(_with=list(non_mailman_groups), _from=dt_now, until=dt_now, how=None): e = non_mailman_groups[rel['with']] email = id2email.get(rel['who']) if email is not None: tbl[e.canonical_email].append(email) return tbl
def relation_begin(request): # TODO We should use Django forms, or better: use sweet Ajax d = {} for t in ('who', 'with', 'how'): if t not in request.POST: raise ValueError, "Missing attr %s" % t if t == 'how' and request.POST[t] == 'null': d[t] = None else: d[t] = _id(request.POST[t]) if not Es.user_may_begin_relation(request.user, d['who'], d['with'], d['how']): raise PermissionDenied # Check whether such a relation already exists dt = now() ok = False try: next(Es.query_relations(who=d['who'], _with=d['with'], how=d['how'], _from=dt, until=DT_MAX)) except StopIteration: ok = True if not ok: raise ValueError, "This relation already exists" # Add the relation! Es.add_relation(d['who'], d['with'], d['how'], dt, DT_MAX) giedo.sync() return redirect_to_referer(request)
def relation_begin(request): # TODO We should use Django forms, or better: use sweet Ajax d = {} for t in ("who", "with", "how"): if t not in request.POST: raise ValueError, "Missing attr %s" % t if t == "how" and (not request.POST[t] or request.POST[t] == "null"): d[t] = None else: d[t] = _id(request.POST[t]) if not Es.user_may_begin_relation(request.user, d["who"], d["with"], d["how"]): raise PermissionDenied # Check whether such a relation already exists dt = now() ok = False try: next(Es.query_relations(who=d["who"], _with=d["with"], how=d["how"], _from=dt, until=DT_MAX)) except StopIteration: ok = True if not ok: raise ValueError, _("Deze relatie bestaat al") # Add the relation! relation_id = Es.add_relation(d["who"], d["with"], d["how"], dt, DT_MAX) # Notify informacie # TODO (rik) leave out 'als lid' Es.notify_informacie("relation_begin", request.user, relation=relation_id) giedo.sync_async(request) return redirect_to_referer(request)
def generate_postfix_slm_map(giedo): # We generate the postfix "sender_login_maps". # This is used to decide by postfix whether a given user is allowed to # send e-mail as if it was coming from a particular e-mail address. # It is a dictionary { <email address> : [ <allowed user>, ... ] } tbl = dict() dt_now = now() # Get all users ulut = dict() # We only allow members to send e-mail for u in Es.by_name('leden').get_members(): ulut[u._id] = u for name in u.names: if str(name) not in tbl: tbl[str(name)] = set() tbl[str(name)].add(str(u.name)) # There are two kind of groups: groups whose members are allowed to # send e-mail as if coming from the group itself and those where this # is not allowed. For convenience, lets call the first kind the # impersonatable groups. # Get all impersonatable groups and create a look-up-table for # group membership gs = list() for g in Es.groups(): # TODO add a tag to force a group either impersonatable or not if not g.got_mailman_list: gs.append(g) mrels = Es.query_relations(how=None, _with=gs, _from=dt_now, until=dt_now) mlut = dict() for g in gs: mlut[g._id] = [] for mrel in mrels: mlut[mrel['with']].append(mrel['who']) # Flatten out group membership. For instance: if Giedo is in Kasco # and Kasco is in Boekenlezers, then Giedo is also in the Boekenlezers # unix group. # But first split the mlut graph into a impersonatable group # and a non-group subgraph. mlut_g = {} # { <group> : <members that are impersonatable groups> } mlut_u = {} # { <group> : <members that are users> } for g_id in mlut: mlut_g[g_id] = [c for c in mlut[g_id] if c in mlut] mlut_u[g_id] = [c for c in mlut[g_id] if c in ulut] mlut_g_tc = tc(mlut_g) # transitive closure for g in gs: to_consider = tuple(mlut_g_tc[g._id]) + (g._id,) for sg_id in to_consider: for u_id in mlut_u[sg_id]: for name in g.names: if str(name) not in tbl: tbl[str(name)] = set() tbl[str(name)].add(str(ulut[u_id].name)) # Clean up tbl to return. ret = {} for name, users in six.iteritems(tbl): if not users: continue ret["%s@%s" % (name, settings.MAILDOMAIN)] = tuple(users) return ret
def add_note(self, what, by=None): dt = now() note = Note({'note': what, 'on': self._id, 'by': None if by is None else _id(by), 'at': dt}) note.save() return note
def notify_informacie(event, entity=None, relation=None): data = {'when': now(), 'event': event} if relation is not None: data['rel'] = _id(relation) elif entity is not None: data['entity'] = _id(entity) else: raise ValueError, 'supply either entity or relation' incol.insert(data)
def add_note(self, what, by=None): dt = now() Note({'note': what, 'on': self._id, 'open': True, 'by': None if by is None else _id(by), 'at': dt, 'closed_by': None, 'closed_at': None}).save()
def get_current_and_old_members(self): dt = now() cur, _all = set(), set() for rel in self.get_rrelated(how=None, deref_with=False): _all.add(rel['who']) if ((rel['until'] is None or rel['until'] >= dt) and (rel['from'] is None or rel['from'] <= dt)): cur.add(rel['who']) return (cur, _all - cur)
def generate_unix_map(giedo): ret = {'groups': {}, 'users': {}} dt_now = now() # Get all users ulut = dict() for u in Es.users(): if not u.got_unix_user: continue ulut[u._id] = u ret['users'][str(u.name)] = { 'full_name': u.full_name, 'expire_date': DT_MIN.strftime('%Y-%m-%d')} member_relations_grouped = dict() for rel in Es.query_relations(_with=Es.by_name('leden'), until=dt_now): if rel['who'] not in member_relations_grouped: member_relations_grouped[rel['who']] = [] member_relations_grouped[rel['who']].append(rel) for user_id, relations in member_relations_grouped.items(): latest = max(relations, key=lambda x: x['until']) ret['users'][str(ulut[user_id].name)]['expire_date'] \ = latest['until'].strftime('%Y-%m-%d') # Get all groups and create a look-up-table for group membership gs = tuple(Es.groups()) mrels = Es.query_relations(how=None, _with=gs, _from=dt_now, until=dt_now) mlut = dict() for g in gs: mlut[g._id] = [] for mrel in mrels: mlut[mrel['with']].append(mrel['who']) # Flatten out group membership. For instance: if Giedo is in Kasco # and Kasco is in Boekenlezers, then Giedo is also in the Boekenlezers # unix group. # But first split the mlut graph into a group and a non-group subgraph. mlut_g = {} # { <group> : <members that are groups> } mlut_ng = {} # { <group> : <members that are not groups> } for g_id in mlut: mlut_g[g_id] = [c for c in mlut[g_id] if c in mlut] mlut_ng[g_id] = [c for c in mlut[g_id] if c not in mlut] mlut_g_tc = tc(mlut_g) # transitive closure # Generate the { <group> : <indirect non-group members> } graph memb_graph = {} for g in gs: if not g.got_unix_group: continue memb_graph[g._id] = set(mlut_ng[g._id]) for h_id in mlut_g_tc[g._id]: memb_graph[g._id].update(mlut_ng[h_id]) # Fill the return map for g in gs: if not g.got_unix_group: continue ret['groups'][str(g.name)] = [str(ulut[c].name) for c in memb_graph[g._id] if c in ulut] return ret
def is_related_with(self, whom, how=None): dt = now() how = None if how is None else _id(how) return rcol.find_one( {'who': _id(self), 'how': how, 'from': {'$lte': dt}, 'until': {'$gte': dt}, 'with': _id(whom)}, {'_id': True}) is not None
def generate_unix_map(giedo): ret = {'groups': {}, 'users': {}} dt_now = now() # Get all users ulut = dict() for u in Es.users(): if not u.got_unix_user: continue ulut[u._id] = u ret['users'][str(u.name)] = { 'full_name': u.full_name, 'expire_date': DT_MIN.strftime('%Y-%m-%d')} member_relations_grouped = dict() for rel in Es.query_relations(_with=Es.by_name('leden'), until=dt_now): if rel['who'] not in member_relations_grouped: member_relations_grouped[rel['who']] = [] member_relations_grouped[rel['who']].append(rel) for user_id, relations in member_relations_grouped.items(): latest = max(relations, key=lambda x: x['until']) ret['users'][str(ulut[user_id].name)]['expire_date'] = ( latest['until'].strftime('%Y-%m-%d')) # Get all groups and create a look-up-table for group membership gs = tuple(Es.groups()) mrels = Es.query_relations(how=None, _with=gs, _from=dt_now, until=dt_now) mlut = dict() for g in gs: mlut[g._id] = [] for mrel in mrels: mlut[mrel['with']].append(mrel['who']) # Flatten out group membership. For instance: if Giedo is in Kasco # and Kasco is in Boekenlezers, then Giedo is also in the Boekenlezers # unix group. # But first split the mlut graph into a group and a non-group subgraph. mlut_g = {} # { <group> : <members that are groups> } mlut_ng = {} # { <group> : <members that are not groups> } for g_id in mlut: mlut_g[g_id] = [c for c in mlut[g_id] if c in mlut] mlut_ng[g_id] = [c for c in mlut[g_id] if c not in mlut] mlut_g_tc = tc(mlut_g) # transitive closure # Generate the { <group> : <indirect non-group members> } graph memb_graph = {} for g in gs: if not g.got_unix_group: continue memb_graph[g._id] = set(mlut_ng[g._id]) for h_id in mlut_g_tc[g._id]: memb_graph[g._id].update(mlut_ng[h_id]) # Fill the return map for g in gs: if not g.got_unix_group: continue ret['groups'][str(g.name)] = [str(ulut[c].name) for c in memb_graph[g._id] if c in ulut] return ret
def add_note(self, what, by=None): dt = now() note = Note({ 'note': what, 'on': self._id, 'by': None if by is None else _id(by), 'at': dt }) note.save() return note
def cached_groups(self): """ The list of entities this user is None-related with. This field is cached. """ if not hasattr(self, '__groups_cache'): dt = now() self.__groups_cache = [rel['with'] for rel in self.get_related( None, dt, dt, False, True, False)] return self.__groups_cache
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)
def update_primary_telephone(self, new, save=True): """ Adds @new as new and primary telephone number. """ if 'telephones' not in self._data: self._data['telephones'] = [] addrs = self._data['telephones'] dt = now() if addrs: addrs[0]['until'] = dt addrs.insert(0, {'number': new, 'from': dt, 'until': DT_MAX}) if save: self.save()
def update_primary_email(self, new, save=True): """ Adds @new as new and primary e-mail address. """ if 'emailAddresses' not in self._data: self._data['emailAddresses'] = [] addrs = self._data['emailAddresses'] dt = now() if addrs: addrs[0]['until'] = dt addrs.insert(0, {'email': new, 'from': dt, 'until': DT_MAX}) if save: self.save()
def add_note(self, what, by=None): dt = now() Note({ 'note': what, 'on': self._id, 'open': True, 'by': None if by is None else _id(by), 'at': dt, 'closed_by': None, 'closed_at': None }).save()
def cached_groups(self): """ The list of entities this user is None-related with. This field is cached. """ if not hasattr(self, '_groups_cache'): dt = now() self._groups_cache = [ rel['with'] for rel in self.get_related(None, dt, dt, False, True, False) ] return self._groups_cache
def generate_mailman_changes(giedo): todo = {'create': [], 'add': {}, 'remove': {}} # TODO mm_groups and mm_rels # Get the groups that need a mailing list and the members of those # groups. dt_now = now() mm_groups = [g for g in Es.groups() if g.got_mailman_list and g.name] mm_rels = Es.query_relations(_with=mm_groups, how=None, _from=dt_now, until=dt_now, deref_who=True) # Get the current mailing list membersip ml_membership = giedo.hans.send({'type': 'maillist-get-membership'}) # membership_to_check will contain the current membership of the # mailinglists for which there are groups. We will remove users # from membership_to_check if we see these users are in the groups. # In the end membership_to_check contains the stray users. membership_to_check = {} gid2name = dict() # Find the current members of the mailing lists and find which # mailing lists are missing. for g in mm_groups: name = str(g.name).encode() gid2name[g._id] = name if name in ml_membership: membership_to_check[name] = set(ml_membership[name]) else: todo['create'].append((name, six.text_type(g.humanName))) membership_to_check[name] = set() # Check which memberships are missing in the current mailing lists for rel in mm_rels: em = rel['who'].canonical_email.encode() gname = gid2name[rel['with']] if em not in membership_to_check[gname]: if gname not in todo['add']: todo['add'][gname] = [] todo['add'][gname].append(em) else: membership_to_check[gname].remove(em) # Check which memberships are superfluous in the current mailing lists for n in ml_membership: if n not in membership_to_check: logging.warning("Unaccounted e-maillist %s" % n) continue for em in membership_to_check[n]: if n not in todo['remove']: todo['remove'][n] = [] todo['remove'][n].append(em) return todo
def _entity_detail(request, e): def _cmp(x,y): r = Es.relation_cmp_until(y,x) if r: return r r = cmp(unicode(x['with'].humanName), unicode(y['with'].humanName)) if r: return r r = cmp(unicode(x['how'].humanName) if x['how'] else None, unicode(y['how'].humanName) if y['how'] else None) if r: return r return Es.relation_cmp_from(x,y) def _rcmp(x,y): r = Es.relation_cmp_until(y,x) if r: return r r = cmp(unicode(x['how'].humanName) if x['how'] else None, unicode(y['how'].humanName) if y['how'] else None) if r: return r r = cmp(unicode(x['who'].humanName), unicode(y['who'].humanName)) if r: return r return Es.relation_cmp_from(x,y) related = sorted(e.get_related(), cmp=_cmp) rrelated = sorted(e.get_rrelated(), cmp=_rcmp) for r in chain(related, rrelated): r['may_end'] = Es.user_may_end_relation(request.user, r) r['id'] = r['_id'] r['until_year'] = (None if r['until'] is None else Es.date_to_year(r['until'])) r['virtual'] = Es.relation_is_virtual(r) tags = [t.as_primary_type() for t in e.get_tags()] ctx = {'related': related, 'rrelated': rrelated, 'now': now(), 'tags': sorted(tags, Es.entity_cmp_humanName), 'object': e} # Is request.user allowed to add (r)relations? if ('secretariaat' in request.user.cached_groups_names and (e.is_group or e.is_user)): groups = [g for g in Es.groups() if not g.is_virtual] groups.sort(cmp=lambda x,y: cmp(unicode(x.humanName), unicode(y.humanName))) users = sorted(Es.users(), cmp=Es.entity_cmp_humanName) brands = sorted(Es.brands(), cmp=Es.entity_cmp_humanName) ctx.update({'users': users, 'brands': brands, 'groups': groups, 'may_add_related': True, 'may_add_rrelated': True}) if e.is_tag: ctx.update({'tag_bearers': sorted(e.as_tag().get_bearers(), cmp=Es.entity_cmp_humanName)}) return ctx
def may_manage_planning(user): if user is None: return False if not hasattr(user, 'cached_groups_names'): # e.g. AnonymousUser return False if user.cached_groups_names & PLANNER_GROUPS: return True dt = now() if len(list(user.get_related(Es.by_name('!brand-planner'), dt, dt, False, False, False))): # The user has any role 'planner' so they may manage it. return True return False
def end_rel(who, _with, how, at=None): """ Ends a relation given by names. For instance: end_rel('giedo', 'leden', None, '2012-04-09') """ who = Es.id_by_name(who) _with = Es.id_by_name(_with) how = (Es.ecol.find_one({'sofa_suffix': how})['_id'] if how is not None else None) at = str_to_date(at) if at is not None else now() Es.rcol.update({'who': who, 'with': _with, 'how': how, 'until': DT_MAX}, {'$set': {'until': at}})
def _group_detail(request, group): ctx = _entity_detail(request, group) isFreeToJoin = group.has_tag(Es.id_by_name("!free-to-join", True)) rel_id = None if isFreeToJoin: dt = now() rel = list(Es.query_relations(request.user, group, None, dt, dt, False, False, False)) assert len(rel) <= 1 for r in rel: rel_id = r["_id"] ctx.update( {"isFreeToJoin": group.has_tag(Es.by_name("!free-to-join")), "request": request, "relation_with_group": rel_id} ) return render_to_response("leden/group_detail.html", ctx, context_instance=RequestContext(request))
def _sync_villanet(self): ret = self.villanet_request({'action': 'listUsers'}) if not ret[0]: return ret = json.loads(ret[1]) users = dict() ulut = dict() for u in Es.users(): ulut[u._id] = str(u.name) member_relations_grouped = dict() for rel in Es.query_relations(_with=Es.by_name('leden'), until=now()): if rel['who'] not in member_relations_grouped: member_relations_grouped[rel['who']] = [] member_relations_grouped[rel['who']].append(rel) for user_id, relations in member_relations_grouped.items(): latest = max(relations, key=lambda x: x['until']) users[ulut[user_id]] = latest['until'].strftime('%Y-%m-%d') vn = set(ret.keys()) kn = set(users.keys()) dt_max = settings.DT_MAX.strftime('%Y-%m-%d') for name in kn - vn: data = { 'username': name, 'password': self.villanet_encrypt_password(pseudo_randstr(16)), } if users[name] != dt_max: data['till'] = users[name] pc = Es.PushChange({ 'system': 'villanet', 'action': 'addUser', 'data': data }) pc.save() for name in vn - kn: logging.info("Stray user %s" % name) for name in vn & kn: remote = (ret[name]['till'][:10] if ret[name]['till'] is not None else '') local = users[name] if users[name] != dt_max else '' if remote != local: pc = Es.PushChange({ 'system': 'villanet', 'action': 'changeUser', 'data': { 'username': name, 'till': local } }) pc.save() self.push_changes_event.set()
def planning_view(request): if 'lookbehind' in request.GET: lookbehind = int(request.GET['lookbehind']) else: lookbehind = 1 pools = list(Pool.all()) poolid2index = dict() poolids = set() for pool in pools: poolids.add(_id(pool)) # TODO reduce number of queries event_entities = list(Event.all_since_datetime(date_to_midnight(now()) - datetime.timedelta(days=lookbehind))) used_pools = set() for e in event_entities: for v in e.vacancies(): used_pools.add(v.pool_id) pools_tpl = [] i = 0 for pool in pools: if _id(pool) not in used_pools: continue poolid2index[pool._id] = i pools_tpl.append(pool) i += 1 events = list() for e in event_entities: ei = { 'id': _id(e), 'name': e.name, 'datetime': e.date, 'kind': e.kind, 'vacancies': dict()} for index in poolid2index.values(): ei['vacancies'][index] = list() for v in e.vacancies(): ei['vacancies'][poolid2index[v.pool_id]].append({ 'begin': v.begin, 'begin_time': v.begin_time, 'end_time': v.end_time, 'assignee': v.assignee.humanName if v.assignee else "?"}) for index in poolid2index.values(): ei['vacancies'][index].sort(key=lambda x: x['begin']) events.append(ei) events.sort(key=lambda x: x['datetime']) return render_to_response('planning/overview.html', {'events': events, 'pools': pools_tpl}, context_instance=RequestContext(request))
def is_related_with(self, whom, how=None): dt = now() how = None if how is None else _id(how) return rcol.find_one( { 'who': _id(self), 'how': how, 'from': { '$lte': dt }, 'until': { '$gte': dt }, 'with': _id(whom) }, {'_id': True}) is not None
def update_study(self, study, institute, number, save=True): """ Adds (study, institute, number) as new and primary. """ if 'studies' not in self._data: self._data['studies'] = [] studies = self._data['studies'] dt = now() if studies: studies[0]['until'] = dt studies.insert(0, {'study': _id(study), 'institute': _id(institute), 'number': number, 'from': dt, 'until': DT_MAX}) if save: self.save()
def _group_detail(request, group): ctx = _entity_detail(request, group) isFreeToJoin = group.has_tag(Es.id_by_name('!free-to-join', True)) rel_id = None if isFreeToJoin: dt = now() rel = list(Es.query_relations(request.user, group, None, dt, dt, False, False, False)) assert len(rel) <= 1 for r in rel: rel_id = r['_id'] ctx.update({'isFreeToJoin': group.has_tag(Es.by_name('!free-to-join')), 'request': request, 'relation_with_group': rel_id}) return render(request, 'leden/group_detail.html', ctx)
def generate_mailman_changes(hans): todo = hans_pb2.ApplyChangesReq() # TODO mm_groups and mm_rels # Get the groups that need a mailing list and the members of those # groups. dt_now = now() mm_groups = [g for g in Es.groups() if g.got_mailman_list and g.name] mm_rels = Es.query_relations(_with=mm_groups, how=None, _from=dt_now, until=dt_now, deref_who=True) # Get the current mailing list membersip ml_membership = hans.GetMembership(hans_pb2.GetMembershipReq()).membership # membership_to_check will contain the current membership of the # mailinglists for which there are groups. We will remove users # from membership_to_check if we see these users are in the groups. # In the end membership_to_check contains the stray users. membership_to_check = {} gid2name = dict() # Find the current members of the mailing lists and find which # mailing lists are missing. for g in mm_groups: name = str(g.name).encode() gid2name[g._id] = name if name in ml_membership: membership_to_check[name] = set(ml_membership[name].emails) else: todo.create.append(hans_pb2.ListCreateReq( name=name, humanName=six.text_type(g.humanName))) membership_to_check[name] = set() # Check which memberships are missing in the current mailing lists for rel in mm_rels: em = rel['who'].canonical_email.encode() gname = gid2name[rel['with']] if em not in membership_to_check[gname]: todo.add[gname].emails.append(em) else: membership_to_check[gname].remove(em) # Check which memberships are superfluous in the current mailing lists for n in ml_membership: if n not in membership_to_check: logging.warning("Unaccounted e-maillist %s" % n) continue for em in membership_to_check[n]: todo.remove[n].emails.append(em) return todo
def _sync_villanet(self): if not settings.VILLANET_SECRET_API_KEY: logging.warn("VILLANET_SECRET_API_KEY not set") return ret = self.villanet_request({'action': 'listUsers'}) if not ret[0]: return ret = json.loads(ret[1]) users = dict() ulut = dict() for u in Es.users(): ulut[u._id] = str(u.name) member_relations_grouped = dict() for rel in Es.query_relations(_with=Es.by_name('leden'), until=now()): if rel['who'] not in member_relations_grouped: member_relations_grouped[rel['who']] = [] member_relations_grouped[rel['who']].append(rel) for user_id, relations in member_relations_grouped.items(): latest = max(relations, key=lambda x: x['until']) users[ulut[user_id]] = latest['until'].strftime('%Y-%m-%d') vn = set(ret.keys()) kn = set(users.keys()) dt_max = settings.DT_MAX.strftime('%Y-%m-%d') for name in kn - vn: data = { 'username': name, 'password': self.villanet_encrypt_password( pseudo_randstr(16)), } if users[name] != dt_max: data['till'] = users[name] pc = Es.PushChange({'system': 'villanet', 'action': 'addUser', 'data': data}) pc.save() for name in vn - kn: logging.info("Stray user %s" % name) for name in vn & kn: remote = (ret[name]['till'][:10] if ret[name]['till'] is not None else '') local = users[name] if users[name] != dt_max else '' if remote != local: pc = Es.PushChange({'system': 'villanet', 'action': 'changeUser', 'data': { 'username': name, 'till': local }}) pc.save() self.push_changes_event.set()
def _group_detail(request, group): ctx = _entity_detail(request, group) isFreeToJoin = group.has_tag(Es.id_by_name('!free-to-join', True)) rel_id = None if isFreeToJoin: dt = now() rel = list(Es.query_relations(request.user, group, None, dt, dt, False, False, False)) assert len(rel) <= 1 for r in rel: rel_id = r['_id'] ctx.update({'isFreeToJoin': group.has_tag(Es.by_name('!free-to-join')), 'request': request, 'relation_with_group': rel_id}) return render_to_response('leden/group_detail.html', ctx, context_instance=RequestContext(request))
def may_manage_planning(user): if user is None: return False if not hasattr(user, 'cached_groups_names'): # e.g. AnonymousUser return False if user.cached_groups_names & PLANNER_GROUPS: return True dt = now() if len( list( user.get_related(Es.by_name('!brand-planner'), dt, dt, False, False, False))): # The user has any role 'planner' so they may manage it. return True return False
def update_address(self, street, number, _zip, city, save=True): """ Adds (street, number, _zip, city) as new and primary address. """ if 'addresses' not in self._data: self._data['addresses'] = [] addresses = self._data['addresses'] dt = now() if addresses: addresses[0]['until'] = dt addresses.insert(0, {'street': street, 'number': number, 'zip': _zip, 'city': city, 'from': dt, 'until': DT_MAX}) if save: self.save()
def generate_mailman_changes(giedo): todo = {'create': [], 'add': {}, 'remove': {}} # TODO mm_groups and mm_rels # Get the groups that need a mailing list and the members of those # groups. dt_now = now() mm_groups = [g for g in Es.groups() if g.got_mailman_list and g.name] mm_rels = Es.query_relations(_with=mm_groups, how=None, _from=dt_now, until=dt_now, deref_who=True) # TODO do we want to cache these? # Get the current mailing lists ml_names = frozenset(Mailman.Utils.list_names()) ml_members = dict() gid2name = dict() # Find the current members of the mailing lists and find which # mailing lists are missing. for g in mm_groups: gid2name[g._id] = str(g.name) if not str(g.name) in ml_names: todo['create'].append((str(g.name), unicode(g.humanName))) c_ms = set([]) else: c_ms = set([x[0] for x in Mailman.MailList.MailList(str(g.name), lock=False).members.iteritems()]) ml_members[str(g.name)] = c_ms # Check which memberships are missing in the current mailing lists for rel in mm_rels: em = rel['who'].canonical_email gname = gid2name[rel['with']] if not em in ml_members[gname]: if not gname in todo['add']: todo['add'][gname] = [] todo['add'][gname].append(em) else: ml_members[gname].remove(em) # Check which memberships are superfluous in the current mailing lists for n in ml_names: if not n in ml_members: logging.warning("Unaccounted e-maillist %s" % n) continue for em in ml_members[n]: if not n in todo['remove']: todo['remove'][n] = [] todo['remove'][n].append(em) return todo
def end_rel(who, _with, how, at=None): """ Ends a relation given by names. For instance: end_rel('giedo', 'leden', None, '2012-04-09') """ who = Es.id_by_name(who) _with = Es.id_by_name(_with) how = (Es.ecol.find_one({'sofa_suffix': how})['_id'] if how is not None else None) at = str_to_date(at) if at is not None else now() Es.rcol.update({ 'who': who, 'with': _with, 'how': how, 'until': DT_MAX }, {'$set': { 'until': at }})
def update_study(self, study, institute, number, save=True): """ Adds (study, institute, number) as new and primary. """ if 'studies' not in self._data: self._data['studies'] = [] studies = self._data['studies'] dt = now() if studies: studies[0]['until'] = dt studies.insert( 0, { 'study': _id(study), 'institute': _id(institute), 'number': number, 'from': dt, 'until': DT_MAX }) if save: self.save()
def planning_view(request): if 'lookbehind' in request.GET: lookbehind = int(request.GET['lookbehind']) else: lookbehind = 1 pools = list(Pool.all()) poolid2idx = dict() i = 0 for pool in pools: poolid2idx[pool._id] = i i += 1 events = list() # TODO reduce number of queries for e in Event.all_since_datetime( date_to_midnight(now()) - datetime.timedelta(days=lookbehind)): ei = { 'name': e.name, 'datetime': e.date, 'kind': e.kind, 'vacancies': dict() } for idx in poolid2idx.values(): ei['vacancies'][idx] = list() for v in e.vacancies(): ei['vacancies'][poolid2idx[v.pool_id]].append({ 'begin': v.begin, 'begin_time': v.begin_time, 'end_time': v.end_time, 'assignee': v.assignee.get_user().humanName if v.assignee else "?" }) for idx in poolid2idx.values(): ei['vacancies'][idx].sort(key=lambda x: x['begin']) events.append(ei) events.sort(key=lambda x: x['datetime']) return render_to_response('planning/overview.html', { 'events': events, 'pools': pools, 'poolcount': len(pools) }, context_instance=RequestContext(request))
def update_address(self, street, number, _zip, city, save=True): """ Adds (street, number, _zip, city) as new and primary address. """ if 'addresses' not in self._data: self._data['addresses'] = [] addresses = self._data['addresses'] dt = now() if addresses: addresses[0]['until'] = dt addresses.insert( 0, { 'street': street, 'number': number, 'zip': _zip, 'city': city, 'from': dt, 'until': DT_MAX }) if save: self.save()
def relation_begin(request): # TODO We should use Django forms, or better: use sweet Ajax d = {} for t in ('who', 'with', 'how'): if t not in request.POST: raise ValueError, "Missing attr %s" % t if t == 'how' and (not request.POST[t] or request.POST[t] == 'null'): d[t] = None else: d[t] = _id(request.POST[t]) if not Es.user_may_begin_relation(request.user, d['who'], d['with'], d['how']): raise PermissionDenied # Check whether such a relation already exists dt = now() ok = False try: next(Es.query_relations(who=d['who'], _with=d['with'], how=d['how'], _from=dt, until=DT_MAX)) except StopIteration: ok = True if not ok: raise ValueError, "This relation already exists" # Add the relation! Es.add_relation(d['who'], d['with'], d['how'], dt, DT_MAX) # Notify informacie if request.user._id == d['who']: Es.notify_informacie("%s heeft zich ingeschreven als %s %s" % ( request.user.full_name, Es.by_id(d['how']).humanName if d['how'] else 'lid', Es.by_id(d['with']).humanName.genitive)) else: # TODO (rik) leave out 'als lid' Es.notify_informacie("%s is nu %s %s" % ( Es.by_id(d['who']).humanName, Es.by_id(d['how']).humanName if d['how'] else 'lid', Es.by_id(d['with']).humanName.genitive)) giedo.sync_async(request) return redirect_to_referer(request)
def check_email(): dt_now = now() comm_ids = map(_id, Es.by_name('comms').get_bearers()) list_ids = map(_id, Es.by_name('lists-opted').get_bearers()) with open('check-email.template') as f: template_text = StringIO() for line in f: if line.endswith("\\\n"): template_text.write(line[:-2]) else: template_text.write(line) templ = Template(template_text.getvalue()) for m in args_to_users(sys.argv[1:]): rels = m.get_related() rels = sorted(rels, cmp=lambda x, y: cmp(str(x['with'].humanName), str(y['with'].humanName))) comms = [] lists = [] others = [] for rel in rels: if Es.relation_is_virtual(rel): continue if _id(rel['with']) in comm_ids: comms.append(rel) elif _id(rel['with']) in list_ids: lists.append(rel) else: others.append(rel) print m.name em = templ.render( Context({ 'u': m, 'comms': comms, 'lists': lists, 'others': others })) send_mail('Controle Karpe Noktem ledenadministratie', em, '*****@*****.**', [m.primary_email])
def relation_begin(request): # TODO We should use Django forms, or better: use sweet Ajax d = {} for t in ('who', 'with', 'how'): if t not in request.POST: raise ValueError("Missing attr %s" % t) if t == 'how' and (not request.POST[t] or request.POST[t] == 'null'): d[t] = None else: d[t] = _id(request.POST[t]) if not Es.user_may_begin_relation(request.user, d['who'], d['with'], d['how']): raise PermissionDenied # Check whether such a relation already exists dt = now() ok = False try: next( Es.query_relations(who=d['who'], _with=d['with'], how=d['how'], _from=dt, until=DT_MAX)) except StopIteration: ok = True if not ok: messages.info(request, _("Deze relatie bestaat al")) return redirect_to_referer(request) # Add the relation! relation_id = Es.add_relation(d['who'], d['with'], d['how'], dt, DT_MAX) # Notify informacie # TODO (rik) leave out 'als lid' Es.notify_informacie('relation_begin', request.user, relation=relation_id) giedo.sync_async(request) return redirect_to_referer(request)
def planning_view(request): if 'lookbehind' in request.GET: lookbehind = int(request.GET['lookbehind']) else: lookbehind = 1 pools = list(Pool.all()) poolid2idx = dict() i = 0 for pool in pools: poolid2idx[pool._id] = i i += 1 events = list() # TODO reduce number of queries for e in Event.all_since_datetime(date_to_midnight(now()) - datetime.timedelta(days=lookbehind)): ei = { 'name': e.name, 'datetime': e.date, 'kind': e.kind, 'vacancies': dict()} for idx in poolid2idx.values(): ei['vacancies'][idx] = list() for v in e.vacancies(): ei['vacancies'][poolid2idx[v.pool_id]].append({ 'begin': v.begin, 'begin_time': v.begin_time, 'end_time': v.end_time, 'assignee': v.assignee.humanName if v.assignee else "?"}) for idx in poolid2idx.values(): ei['vacancies'][idx].sort(key=lambda x: x['begin']) events.append(ei) events.sort(key=lambda x: x['datetime']) return render_to_response('planning/overview.html', {'events': events, 'pools': pools, 'poolcount': len(pools)}, context_instance=RequestContext(request))
def check_email(): dt_now = now() comm_ids = map(_id, Es.by_name('comms').get_bearers()) list_ids = map(_id, Es.by_name('lists-opted').get_bearers()) with open('check-email.template') as f: template_text = StringIO() for line in f: if line.endswith("\\\n"): template_text.write(line[:-2]) else: template_text.write(line) templ = Template(template_text.getvalue()) for m in args_to_users(sys.argv[1:]): rels = m.get_related() rels = sorted(rels, cmp=lambda x,y: cmp(str(x['with'].humanName), str(y['with'].humanName))) comms = [] lists = [] others = [] for rel in rels: if Es.relation_is_virtual(rel): continue if _id(rel['with']) in comm_ids: comms.append(rel) elif _id(rel['with']) in list_ids: lists.append(rel) else: others.append(rel) print m.name em = templ.render(Context({ 'u': m, 'comms': comms, 'lists': lists, 'others': others})) send_mail('Controle Karpe Noktem ledenadministratie', em, '*****@*****.**', [m.primary_email])
def planning_manage(request, poolname): if not may_manage_planning(request.user): raise PermissionDenied pool = Pool.by_name(poolname) if pool is None: raise Http404 if not pool.may_manage(request.user): raise PermissionDenied # TODO reduce number of queries events = dict() for e in Event.all_in_future(): eid = _id(e) vacancies = list(e.vacancies(pool=pool)) events[eid] = { 'vacancies': vacancies, 'date': e.date.date(), 'name': e.name, 'kind': e.kind, 'id': eid } posted = False events[eid]['vacancies'].sort(key=lambda v: v.begin) if request.method == 'POST' and _id(request.POST['eid']) == eid: events[eid]['form'] = ManagePlanningForm( request.POST, pool=pool, vacancies=events[eid]['vacancies']) posted = True else: events[eid]['form'] = ManagePlanningForm( pool=pool, vacancies=events[eid]['vacancies']) if posted and events[eid]['form'].is_valid(): for vacancy in events[eid]['vacancies']: worker = request.POST['shift_%s' % vacancy._id] if worker == '': vacancy.assignee = None vacancy.reminder_needed = True else: if vacancy.assignee_id is None or \ _id(vacancy.assignee_id) != _id(worker): delta = datetime.timedelta(days=5) vacancy.reminder_needed = now() + delta < e.date vacancy.assignee_id = _id(worker) vacancy.save() workers = pool.workers() # XXX het is cooler de shift dichtstbijzijnd aan de vacancy te # zoeken. Stel dat iemand over een half-jaar al is ingepland # dan is dat niet zo boeiend. Terwijl hij nu geen enkele # bardienst meer zou krijgen shifts = pool.last_shifts() for eid in events: for vacancy in events[eid]['vacancies']: vacancy.suggestions = list() workers_by_score = dict() for worker in workers: score = planning_vacancy_worker_score(vacancy, worker) if score not in workers_by_score: workers_by_score[score] = list() workers_by_score[score].append(worker) found_scores = list(workers_by_score.keys()) found_scores.sort(reverse=True) for score in found_scores: scorers = workers_by_score[score] shuffle(scorers) scorers.sort(key=lambda x: shifts[_id(x)] if shifts[_id(x)] else DT_MIN.date()) for scorer in scorers: vacancy.suggestions.append({ 'scorer': scorer, 'score': score }) events = list(events.values()) events.sort(key=lambda e: e['date']) return render(request, 'planning/manage.html', { 'events': events, 'pool': pool })
def planning_view(request): period = 'now' if request.GET.get('year') in {'past1', 'past2'}: period = request.GET['year'] pools = list(Pool.all()) poolid2index = dict() poolids = set() for pool in pools: poolids.add(_id(pool)) # TODO reduce number of queries current = date_to_midnight(now() - datetime.timedelta(days=1)) past1year = date_to_midnight(now() - datetime.timedelta(days=356)) past2year = date_to_midnight(now() - datetime.timedelta(days=356 * 2)) if period == 'now': start = current end = None elif period == 'past1': start = past1year end = current elif period == 'past2': start = past2year end = past1year event_entities = list(Event.all_in_period(start, end)) used_pools = set() for e in event_entities: for v in e.vacancies(): used_pools.add(v.pool_id) pools_tpl = [] i = 0 for pool in pools: if _id(pool) not in used_pools: continue poolid2index[pool._id] = i pools_tpl.append(pool) i += 1 events = list() for e in event_entities: ei = { 'id': _id(e), 'name': e.name, 'datetime': e.date, 'kind': e.kind, 'vacancies': dict() } for index in poolid2index.values(): ei['vacancies'][index] = list() for v in e.vacancies(): ei['vacancies'][poolid2index[v.pool_id]].append({ 'begin': v.begin, 'begin_time': v.begin_time, 'end_time': v.end_time, 'assignee': v.assignee.humanName if v.assignee else "?" }) for index in poolid2index.values(): ei['vacancies'][index].sort(key=lambda x: x['begin']) events.append(ei) events.sort(key=lambda x: x['datetime']) return render(request, 'planning/overview.html', { 'events': events, 'pools': pools_tpl, 'period': period })
def get_members(self): dt = now() return [r['who'] for r in self.get_rrelated( how=None, _from=dt, until=dt)]
def notify_informacie(event, user, **props): data = {'when': now(), 'event': event} data['user'] = _id(user) for key, value in props.items(): data[key] = _id(value) incol.insert(data)
def end_relation(__id): dt = now() rcol.update({'_id': _id(__id)}, {'$set': {'until': dt}})
def all_in_future(cls): return cls.all_in_period(now(), None)
def secr_add_user(request): if 'secretariaat' not in request.user.cached_groups_names: raise PermissionDenied if request.method == 'POST': form = AddUserForm(request.POST) if form.is_valid(): fd = form.cleaned_data # First, create the entity. u = Es.User({ 'types': ['user'], 'names': [fd['username']], 'humanNames': [{ 'human': fd['first_name'] + ' ' + fd['last_name'] }], 'person': { 'titles': [], 'nick': fd['first_name'], 'given': None, 'family': fd['last_name'], 'dateOfBirth': date_to_dt(fd['dateOfBirth']) }, 'email': fd['email'], 'address': { 'street': fd['addr_street'], 'number': fd['addr_number'], 'zip': fd['addr_zip'], 'city': fd['addr_city'] }, 'telephone': fd['telephone'], 'studies': [{ 'institute': _id(fd['study_inst']), 'study': _id(fd['study']), 'from': DT_MIN, 'until': DT_MAX, 'number': fd['study_number'] }], 'is_active': True, 'password': None }) logging.info("Added user %s" % fd['username']) u.save() # Then, add the relations. groups = ['leden'] if fd['incasso']: groups.append('incasso') else: groups.append('geen-incasso') for group in groups: Es.add_relation(u, Es.id_by_name(group, use_cache=True), _from=date_to_dt(fd['dateJoined'])) for l in fd['addToList']: Es.add_relation(u, Es.id_by_name(l, use_cache=True), _from=now()) # Let giedo synch. to create the e-mail adresses, unix user, etc. # TODO use giedo.async() and let giedo send the welcome e-mail giedo.sync() # Create a new password and send it via e-mail pwd = pseudo_randstr() u.set_password(pwd) giedo.change_password(str(u.name), pwd, pwd) render_then_email("leden/set-password.mail.html", u, { 'user': u, 'password': pwd }) # Send the welcome e-mail render_then_email("leden/welcome.mail.html", u, {'u': u}) Es.notify_informacie('adduser', request.user, entity=u._id) return HttpResponseRedirect( reverse('user-by-name', args=(fd['username'], ))) else: form = AddUserForm() return render(request, 'leden/secr_add_user.html', {'form': form})
def _entity_detail(request, e): related = sorted( e.get_related(), key=lambda x: (Es.DT_MIN - Es.relation_until( x), Es.entity_humanName(x['with']), Es.entity_humanName(x['how']))) rrelated = sorted( e.get_rrelated(), key=lambda x: (Es.DT_MIN - Es.relation_until( x), Es.entity_humanName(x['how']), Es.entity_humanName(x['who']))) for r in chain(related, rrelated): r['may_end'] = Es.user_may_end_relation(request.user, r) r['id'] = r['_id'] r['until_year'] = (None if r['until'] is None or r['until'] >= now() else Es.date_to_year(r['until'])) r['virtual'] = Es.relation_is_virtual(r) tags = [t.as_primary_type() for t in e.get_tags()] # mapping of year => set of members year_sets = {} for r in rrelated: year = r['until_year'] if year is None: year = 'this' if year not in year_sets: year_sets[year] = set() year_sets[year].add(r['who']) year_counts = {} for year in year_sets: year_counts[year] = len(year_sets[year]) ctx = { 'related': related, 'rrelated': rrelated, 'year_counts': year_counts, 'now': now(), 'tags': sorted(tags, key=Es.entity_humanName), 'object': e, 'chiefs': [], 'pipos': [], 'reps': [] } for r in rrelated: if r['how'] and Es.relation_is_active(r): if str(r['how'].name) == '!brand-hoofd': r['hidden'] = True ctx['chiefs'].append(r) if str(r['how'].name) == '!brand-bestuurspipo': r['hidden'] = True ctx['pipos'].append(r) if str(r['how'].name) == '!brand-vertegenwoordiger': r['hidden'] = True ctx['reps'].append(r) # Is request.user allowed to add (r)relations? if ('secretariaat' in request.user.cached_groups_names and (e.is_group or e.is_user)): groups = [g for g in Es.groups() if not g.is_virtual] groups.sort(key=Es.entity_humanName) users = sorted(Es.users(), key=Es.entity_humanName) brands = sorted(Es.brands(), key=Es.entity_humanName) ctx.update({ 'users': users, 'brands': brands, 'groups': groups, 'may_add_related': True, 'may_add_rrelated': True, 'may_tag': True, 'may_untag': True }) ctx['may_upload_smoel'] = e.name and request.user.may_upload_smoel_for(e) if e.is_tag: ctx.update({ 'tag_bearers': sorted(e.as_tag().get_bearers(), key=Es.entity_humanName) }) # Check whether entity has a photo photo_size = e.photo_size if e.photo_size is not None: ctx.update({ 'hasPhoto': True, 'photoWidth': photo_size[0], 'photoHeight': photo_size[1] }) return ctx