def unblock(user, processor, when=None): """Unblocks a user. This removes his membership of the ``config.violation`` group. Note that for unblocking, no further asynchronous action has to be triggered, as opposed to e.g. membership termination. :param User user: The user to be unblocked. :param User processor: The admin who unblocked the user. :param datetime when: The time of membership termination. Note that in comparison to :py:func:`suspend`, you don't provide an _interval_, but a point in time, defaulting to the current time. Will be converted to ``closedopen(when, None)``. :return: The unblocked user. """ if when is None: when = session.utcnow() remove_member_of(user=user, group=config.violation_group, processor=processor, during=closedopen(when, None)) message = deferred_gettext(u"User has been unblocked.") log_user_event(message=message.to_json(), author=processor, user=user) return user
def test_intersect(self): self.assertCallEquals(Interval.intersect, [ ((closed(0, 0), closed(0, 0)), closed(0, 0)), ((closed(0, 0), open(0, 0)), None), ((closed(1, 1), closed(0, 2)), closed(1, 1)), ((closed(0, 2), closed(1, 3)), closed(1, 2)), ((closed(0, 3), closed(1, 2)), closed(1, 2)), ((closed(1, 2), closed(0, 3)), closed(1, 2)), ((open(1, 2), closed(0, 3)), open(1, 2)), ((closedopen(1, 2), closed(0, 3)), closedopen(1, 2)), ((openclosed(1, 2), closed(0, 3)), openclosed(1, 2)), ((closed(0, 1), closed(2, 3)), None), ((closed(None, 2), closed(1, None)), closed(1, 2)), ((closed(1, 2), closed(None, None)), closed(1, 2)), ((closed(None, None), closed(None, None)), closed(None, None)), ])
class Params: includes_today = factory.Trait( active_during=interval.closedopen( datetime.now(timezone.utc) - timedelta(1), datetime.now(timezone.utc) + timedelta(1), ), )
def block(user, reason, processor, during=None, violation=True): """Suspend a user during a given interval. The user is added to violation_group or blocked_group in a given interval. A reason needs to be provided. :param User user: The user to be suspended. :param unicode reason: The reason for suspending. :param User processor: The admin who suspended the user. :param Interval|None during: The interval in which the user is suspended. If None the user will be suspendeded from now on without an upper bound. :param Boolean violation: If the user should be added to the violation group :return: The suspended user. """ if during is None: during = closedopen(session.utcnow(), None) if violation: make_member_of(user, config.violation_group, processor, during) else: make_member_of(user, config.blocked_group, processor, during) message = deferred_gettext(u"Suspended during {during}. Reason: {reason}.") log_user_event(message=message.format(during=during, reason=reason).to_json(), author=processor, user=user) return user
def unblock(user, processor, when=None): """Unblocks a user. This removes his membership of the violation, blocken and payment_in_default group. Note that for unblocking, no further asynchronous action has to be triggered, as opposed to e.g. membership termination. :param User user: The user to be unblocked. :param User processor: The admin who unblocked the user. :param datetime when: The time of membership termination. Note that in comparison to :py:func:`suspend`, you don't provide an _interval_, but a point in time, defaulting to the current time. Will be converted to ``closedopen(when, None)``. :return: The unblocked user. """ if when is None: when = session.utcnow() for group in get_blocked_groups(): if user.member_of(group): remove_member_of(user=user, group=group, processor=processor, during=closedopen(when, None)) return user
def _add_membership(group): with session.begin_nested(): m = Membership(user=user, group=group, active_during=closedopen(utcnow, None)) session.add(m) return m
def mem1(self, class_session, user, groups): # mem1: [2000-01-01 ---------------------- 2000-01-03) return MembershipFactory.create( user=user, active_during=closedopen(_iso('2000-01-01'), _iso('2000-01-03')), group=groups[0], )
def mem2(self, class_session, user, groups): # mem2: [2000-01-02 ---------------------- 2000-01-04) return MembershipFactory.create( user=user, active_during=closedopen(_iso('2000-01-02'), _iso('2000-01-04')), group=groups[1], )
def test_serialize_interval(self): self.assertValidSerialization(UnboundedInterval) now = datetime.datetime.utcnow() then = now + datetime.timedelta(1) self.assertValidSerialization(closed(now, then)) self.assertValidSerialization(closedopen(now, then)) self.assertValidSerialization(openclosed(now, then)) self.assertValidSerialization(open(now, then))
def create_user_from1y(self, **kwargs): reg_date = session.utcnow() - timedelta(weeks=52) return UserWithMembershipFactory(registered_at=reg_date, membership__active_during=closedopen( reg_date, None), membership__group=config.member_group, **kwargs)
def test_disable_membership(self, session, utcnow, user, property_group1, property_group2): # add membership to group1 membership = Membership( active_during=closedopen(utcnow - timedelta(hours=2), None), user=user, group=property_group1 ) with session.begin_nested(): session.add(membership) session.refresh(user) assert user.has_property(PROP1) with session.begin_nested(): membership.disable(utcnow - timedelta(hours=1)) session.refresh(user) assert property_group1 not in user.active_property_groups() assert not user.has_property(PROP1) with session.begin_nested(): # add membership to group1 session.add(Membership( active_during=closedopen(utcnow, None), user=user, group=property_group1 )) session.refresh(user) assert user.has_property(PROP1) # add membership to group2 membership = Membership( active_during=closedopen(utcnow - timedelta(hours=2), None), user=user, group=property_group2 ) with session.begin_nested(): session.add(membership) session.refresh(user) assert user.has_property(PROP1) assert user.has_property(PROP2) # disables membership in group2 with session.begin_nested(): membership.disable(utcnow - timedelta(hours=1)) session.refresh(user) assert user.has_property(PROP1) assert not user.has_property(PROP2)
def create_user_move_in_no_grace(self): reg_date = self.last_month_last.replace( day=self.membership_fee_last.booking_end.days) return UserWithMembershipFactory(registered_at=reg_date, membership__active_during=closedopen( reg_date, None), membership__group=config.member_group)
def test_deferred_blocking_and_unblocking_works(self): u = self.user_to_block blockage = session.utcnow() + timedelta(days=1) unblockage = blockage + timedelta(days=2) blocked_user = UserHelper.block(u, reason=u"test", processor=u, during=closedopen(blockage, None)) session.session.commit() blocked_during = closedopen(blockage, unblockage) self.assertEqual(u.log_entries[0].author, blocked_user) self.assert_violation_membership(blocked_user, subinterval=blocked_during) unblocked_user = UserHelper.unblock(blocked_user, processor=u, when=unblockage) session.session.commit() self.assertEqual(unblocked_user.log_entries[0].author, unblocked_user) self.assert_violation_membership(unblocked_user, subinterval=blocked_during)
def test_complement(self): self.assertIntervalSetOperationEquals(IntervalSet.complement, [ ([[]], open(None, None)), ([[closed(0, 1), open(2, 3)] ], [open(None, 0), openclosed(1, 2), closedopen(3, None)]), ([[closed(None, 0), closed(1, None)]], ([open(0, 1)])), ])
def room_history_entries(self, create, extracted, **kwargs): """Create a room history entry, which is required for consistency reasons.""" if create and self.room is not None: # Set room history entry begin to registration date rhe = RoomHistoryEntry.q.filter_by(user=self, room=self.room).one() rhe.active_during = closedopen(self.registered_at, None) for key, value in kwargs.items(): setattr(rhe, key, value)
def test_deferred_blocking_and_unblocking_works(self): u = self.user_to_block blockage = session.utcnow() + timedelta(days=1) unblockage = blockage + timedelta(days=2) blocked_user = UserHelper.suspend(u, reason=u"test", processor=u, during=closedopen(blockage, None)) session.session.commit() blocked_during = closedopen(blockage, unblockage) self.assertEqual(u.log_entries[0].author, blocked_user) self.assert_violation_membership(blocked_user, subinterval=blocked_during) unblocked_user = UserHelper.unblock(blocked_user, processor=u, when=unblockage) session.session.commit() self.assertEqual(unblocked_user.log_entries[0].author, unblocked_user) self.assert_violation_membership(unblocked_user, subinterval=blocked_during)
def test_overlaps(self): self.assertCallTrue(Interval.overlaps, [ (closed(0, 0), closed(0, 0)), (closed(0, 1), closed(1, 2)), (closed(0, 2), closed(1, 3)), (closed(None, 1), closed(0, None)), (closed(0, None), closed(None, 1)), (closed(0, 1), closed(None, None)), (closed(None, None), closed(0, 1)), ]) self.assertCallFalse(Interval.overlaps, [ (closedopen(0, 1), openclosed(1, 2)), (closedopen(0, 1), closed(1, 2)), (closed(0, 1), openclosed(1, 2)), (closed(0, 0), closed(1, 1)), (closed(0, 1), closed(2, 3)), (closed(None, 0), closed(1, None)), (closed(1, None), closed(None, 0)), ])
def test_sort_join(self): self.assertEqual( IntervalSet([ closed(2, 3), closed(2, None), closed(None, 1), closed(1, 3), closed(2, 3), closed(-10, None) ]), IntervalSet([closed(None, None)])) self.assertEqual( IntervalSet([ empty(6), closedopen(1, 2), empty(0), closedopen(2, 3), open(4, 5) ]), IntervalSet([closedopen(1, 3), open(4, 5)]), )
def test_active_instance_property( self, rel_begin, rel_end, active_expected, session, utcnow, user, property_group1 ): interval = closedopen(utcnow + rel_begin, rel_end and utcnow + rel_end) mem = Membership(active_during=interval, user=user, group=property_group1) with session.begin_nested(): session.add(mem) session.refresh(mem) assert (utcnow in mem.active_during) == active_expected
def end_payment_in_default_memberships(processor): users = User.q.join(User.current_properties) \ .filter(CurrentProperty.property_name == 'payment_in_default') \ .join(Account).filter(Account.balance <= 0).all() for user in users: if user.member_of(config.payment_in_default_group): remove_member_of(user, config.payment_in_default_group, processor, closedopen(session.utcnow() - timedelta(seconds=1), None)) return users
def end_payment_in_default_memberships(): processor = User.q.get(0) users = User.q.join(User.current_properties) \ .filter(CurrentProperty.property_name == 'payment_in_default') \ .join(Account).filter(Account.balance <= 0).all() for user in users: remove_member_of(user, config.payment_in_default_group, processor, closedopen(session.utcnow(), None)) return users
def test_add_timed_membership(self, session, utcnow, user, property_group1, property_group2): with session.begin_nested(): session.add(Membership( active_during=closedopen(utcnow, utcnow + timedelta(days=3)), user=user, group=property_group1 )) session.refresh(user) assert user.has_property(PROP1) assert not user.has_property(PROP2) with session.begin_nested(): # add expired membership to group2 session.add(Membership( active_during=closedopen(utcnow - timedelta(hours=2), utcnow - timedelta(hours=1)), user=user, group=property_group2 )) session.refresh(user) assert user.has_property(PROP1) assert not user.has_property(PROP2)
def test_active_disable(self, session, utcnow, user, property_group1): mem = Membership(active_during=closedopen(utcnow - timedelta(hours=2), None), user=user, group=property_group1) with session.begin_nested(): session.add(mem) assert utcnow in mem.active_during # disable: [NOW - 2h,) → [NOW - 2h, NOW - 1h) with session.begin_nested(): mem.disable(utcnow - timedelta(hours=1)) session.refresh(mem) assert utcnow not in mem.active_during
def setup_traffic_group(user, processor, custom_group_id=None, terminate_other=False): """Add a user to a default or custom traffic group If neither a custom group is given, nor the corresponding building has a default traffic group, no membership is added. Group removal is executed independent of the latter. :param User user: the user :param User processor: the processor :param int custom_group_id: the id of a custom traffic group. if ``None``, the traffic group of the building is used. :param bool terminate_other: Whether to terminate current :py:cls:`TrafficGroup` memberships. Defaults to ``False`` """ now = session.utcnow() if terminate_other: for group in user.traffic_groups: remove_member_of(user, group, processor, closedopen(now, None)) traffic_group = determine_traffic_group(user, custom_group_id) if traffic_group is not None: make_member_of(user, traffic_group, processor, closedopen(now, None))
def test_union(self): self.assertIntervalSetOperationEquals(IntervalSet.union, [ ([[], [closed(0, 1), open(1, 2)]], [closed(0, 1), open(1, 2)]), ([[closed(0, 1), open(1, 2)], []], [closed(0, 1), open(1, 2)]), ([[closed(None, 1), closed(3, 4), open(7, 8)], [open(0, 5), closed(6, 7), closedopen(8, None)]], [open(None, 5), closed(6, None)]), ])
def test_join(self): self.assertCallEquals(Interval.join, [ ((closed(0, 0), closed(0, 0)), closed(0, 0)), ((closed(0, 0), closed(0, 1)), closed(0, 1)), ((closed(0, 1), closed(1, 2)), closed(0, 2)), ((closedopen(0, 1), closed(1, 2)), closed(0, 2)), ((closed(0, 0), closed(1, 1)), None), ((closed(None, 0), closed(1, None)), None), ((closed(None, None), closed(1, 2)), closed(None, None)), ((closed(None, 0), closed(0, None)), closed(None, None)), ((closed(None, None), closed(None, None)), closed(None, None)), ])
def test_add_membership(self, session, utcnow, user, property_group1, property_group2): # add membership to group1 with session.begin_nested(): session.add(Membership( active_during=closedopen(utcnow, None), user=user, group=property_group1 )) session.refresh(user) assert user.has_property(PROP1) assert not user.has_property(PROP2) # add membership to group2 with session.begin_nested(): session.add(Membership( active_during=closedopen(utcnow, None), user=user, group=property_group2 )) session.refresh(user) assert user.has_property(PROP1) assert user.has_property(PROP2)
def test_finishes(self): self.assertCallTrue(Interval.finishes, [ (closed(0, 0), closed(0, 0)), (closed(0, 2), closed(1, 2)), (closed(0, None), closed(1, None)), ]) self.assertCallFalse(Interval.finishes, [ (closed(0, 0), closed(1, 1)), (closed(0, 1), closedopen(0, 1)), (closed(0, 1), closed(2, 3)), (closed(None, 0), closed(1, None)), (closed(0, 1), closed(None, 2)), (closed(0, None), closed(None, 1)), ])
def handle_payments_in_default(): processor = User.q.get(0) # Add memberships and end "member" membership if threshold met users = User.q.join(User.current_properties)\ .filter(CurrentProperty.property_name == 'membership_fee') \ .join(Account).filter(Account.balance > 0).all() users_pid_membership = [] users_membership_terminated = [] ts_now = session.utcnow() for user in users: last_pid_membership = Membership.q.filter(Membership.user_id == user.id) \ .filter(Membership.group_id == config.payment_in_default_group.id) \ .order_by(Membership.ends_at.desc()) \ .first() if last_pid_membership is not None: if last_pid_membership.ends_at is not None and \ last_pid_membership.ends_at >= ts_now - timedelta(days=7): continue in_default_days = user.account.in_default_days try: fee = get_membership_fee_for_date( date.today() - timedelta(days=in_default_days)) except NoResultFound: fee = get_last_applied_membership_fee() if not fee: return [], [] if not user.has_property('payment_in_default'): if in_default_days >= fee.payment_deadline.days: make_member_of(user, config.payment_in_default_group, processor, closed(ts_now, None)) users_pid_membership.append(user) if in_default_days >= fee.payment_deadline_final.days: remove_member_of(user, config.member_group, processor, closedopen(ts_now, None)) log_user_event("Mitgliedschaftsende wegen Zahlungsrückstand ({})" .format(fee.name), processor, user) users_membership_terminated.append(user) return users_pid_membership, users_membership_terminated
def handle_payments_in_default(): processor = User.q.get(0) # Add memberships and end "member" membership if threshold met users = User.q.join(User.current_properties)\ .filter(CurrentProperty.property_name == 'membership_fee') \ .join(Account).filter(Account.balance > 0).all() users_pid_membership = [] users_membership_terminated = [] ts_now = session.utcnow() for user in users: last_pid_membership = Membership.q.filter(Membership.user_id == user.id) \ .filter(Membership.group_id == config.payment_in_default_group.id) \ .order_by(Membership.ends_at.desc()) \ .first() if last_pid_membership is not None: if last_pid_membership.ends_at is not None and \ last_pid_membership.ends_at >= ts_now - timedelta(days=7): continue in_default_days = user.account.in_default_days try: fee = get_membership_fee_for_date(date.today() - timedelta(days=in_default_days)) except NoResultFound: fee = get_last_applied_membership_fee() if not fee: return [], [] if not user.has_property('payment_in_default'): if in_default_days >= fee.payment_deadline.days: make_member_of(user, config.payment_in_default_group, processor, closed(ts_now, None)) users_pid_membership.append(user) if in_default_days >= fee.payment_deadline_final.days: remove_member_of(user, config.member_group, processor, closedopen(ts_now, None)) log_user_event( "Mitgliedschaftsende wegen Zahlungsrückstand ({})".format( fee.name), processor, user) users_membership_terminated.append(user) return users_pid_membership, users_membership_terminated
def move_out(user, comment, processor, when, end_membership=True): """Move out a user and may terminate relevant memberships. The user's room is set to ``None`` and all hosts are deleted. Memberships in :py:obj:`config.member_group` and :py:obj:`config.member_group` are terminated. A log message is created including the number of deleted hosts. :param User user: The user to move out. :param unicode|None comment: An optional comment :param User processor: The admin who is going to move out the user. :param datetime when: The time the user is going to move out. :param bool end_membership: Ends membership if true :return: The user that moved out. """ if when > session.utcnow(): raise NotImplementedError( "Moving out in the future is not supported yet.") if end_membership: for group in ({ config.member_group, config.network_access_group, config.external_group } | set(user.traffic_groups)): remove_member_of(user, group, processor, closedopen(when, None)) user.birthdate = None num_hosts = 0 # In case the chain is empty for num_hosts, h in enumerate(user.hosts, 1): session.session.delete(h) user.room = None if comment: message = deferred_gettext( u"Moved out: ({} hosts deleted). Comment: {}").format( num_hosts, comment) else: message = deferred_gettext(u"Moved out: ({} hosts deleted).").format( num_hosts) log_user_event(message=message.to_json(), author=processor, user=user) return user
def create_factories(self): ConfigFactory.create() self.user = UserFactory.create() self.user_membership = MembershipFactory.create( active_during=closedopen(session.utcnow() - timedelta(weeks=52), None), user=self.user, group=config.member_group) last_month_last = session.utcnow().date().replace(day=1) - timedelta(1) self.membership_fee_current = MembershipFeeFactory.create() self.membership_fee_last = MembershipFeeFactory.create( begins_on=last_month_last.replace(day=1), ends_on=last_month_last)
def create_factories(self): super().create_factories() utcnow = self.session.query(func.current_timestamp()).scalar() interval = closedopen(utcnow, None) # We need a user in the same room self.room = factories.RoomFactory() self.similar_user_this_room = factories.UserFactory(room=self.room, name="Tobias Fuenke") self.similar_user_room_history = factories.UserFactory(name="Tobias") self.session.add(RoomHistoryEntry(room=self.room, user=self.similar_user_room_history, active_during=interval)) # nonsimilar users (same room / room history) factories.UserFactory(room=self.room, name="Other dude") self.session.add(RoomHistoryEntry(room=self.room, user=factories.UserFactory(name="Other dude"), active_during=interval))
def test_group_users(self, session, utcnow, user, property_group1, property_group2): assert len(property_group1.users) == 0 assert len(property_group1.active_users()) == 0 # add membership to group1 p1 = Membership(active_during=closedopen(utcnow - timedelta(hours=2), None), user=user, group=property_group1) with session.begin_nested(): session.add(p1) session.refresh(property_group1) assert len(property_group1.users) == 1 assert len(property_group1.active_users()) == 1 with session.begin_nested(): p1.disable(utcnow - timedelta(hours=1)) session.refresh(property_group1) assert len(property_group1.users) == 1 assert len(property_group1.active_users()) == 0
def test_0030_transferred_value(self): amount = Decimal(90) today = session.utcnow().date() simple_transaction( u"transaction", self.fee_account, self.user_account, amount, self.author, today - timedelta(1) ) simple_transaction( u"transaction", self.fee_account, self.user_account, amount, self.author, today ) simple_transaction( u"transaction", self.fee_account, self.user_account, amount, self.author, today + timedelta(1) ) self.assertEqual( transferred_amount( self.fee_account, self.user_account, single(today) ), amount ) self.assertEqual( transferred_amount( self.fee_account, self.user_account, closedopen(today, None) ), 2*amount ) self.assertEqual( transferred_amount( self.fee_account, self.user_account, openclosed(None, today) ), 2*amount ) self.assertEqual( transferred_amount( self.fee_account, self.user_account ), 3*amount ) Transaction.q.delete() session.session.commit()
def suspend(user_id): form = UserSuspendForm() myUser = get_user_or_404(user_id) if form.validate_on_submit(): if form.ends_at.unlimited.data: ends_at = None else: ends_at = datetime.combine(form.ends_at.date.data, utc.time_min()) try: during = closedopen(session.utcnow(), ends_at) blocked_user = lib.user.suspend( user=myUser, reason=form.reason.data, processor=current_user, during=during) session.session.commit() except ValueError as e: flash(str(e), 'error') else: flash(u'Nutzer gesperrt', 'success') return redirect(url_for('.user_show', user_id=user_id)) return render_template('user/user_block.html', form=form, user_id=user_id)