def friendrequest_post_save(sender, instance=None, **kwargs): if instance is None: return fr = instance sent_to = fr.sent_to sent_by = fr.sent_by extra_msgs = [] if fr.answered_at: extra_msgs.append('answered_at=%s' % fr.answered_at.strftime('%Y-%m-%d')) extra_msgs.append('accepted=%s' % fr.accepted) extra = '' if len(extra_msgs): extra = ', ' + ', '.join(extra_msgs) get_logger('baljan.friends').info( 'friend request %s → %s saved%s' % ( sent_by, sent_to, extra)) if fr.answered_at: if fr.accepted: send_type = 'friend_request_accepted' else: send_type = 'friend_request_denied' notifications.send(send_type, sent_by, requestee=sent_to.get_full_name()) else: notifications.send('friend_request_received', sent_to, requestor=sent_by.get_full_name())
def signup_pre_delete(sender, instance=None, **kwargs): if instance is None: return signup = instance signup_post(sender, signup, **kwargs) signup_notice_delete(signup) get_logger('baljan.signups').info("%s deleted" % instance)
def friendrequest_post_delete(sender, instance=None, **kwargs): if instance is None: return fr = instance sent_to = fr.sent_to sent_by = fr.sent_by get_logger('baljan.friends').info('friend request %s → %s deleted' % ( fr.sent_by, fr.sent_to)) if fr.answered_at: pass # a notification has already been sent in this case else: notifications.send('friend_request_canceled', sent_to, requestor=sent_by.get_full_name())
def credits(request): user = request.user profile = user.get_profile() tpl = {} refill_form = baljan.forms.RefillForm() import_form = baljan.forms.ImportOldCardForm() credits_log = get_logger('baljan.views.credits') if request.method == 'POST': try: foo = request.POST['task'] except: credits_log.error('no task in form!') else: if request.POST['task'] == 'refill': refill_form = baljan.forms.RefillForm(request.POST) if refill_form.is_valid(): entered_code = refill_form.cleaned_data['code'] creditsmodule.is_used(entered_code, user) # for logging try: creditsmodule.manual_refill(entered_code, user) tpl['used_card'] = True except creditsmodule.BadCode, e: tpl['invalid_card'] = True elif request.POST['task'] == 'import-old': import_form = baljan.forms.ImportOldCardForm(request.POST) if import_form.is_valid(): entered_code = import_form.cleaned_data['code'] creditsmodule.is_used(entered_code, user, old_card=True) # for logging try: creditsmodule.manual_import(entered_code, user) tpl['used_card'] = True except creditsmodule.BadCode, e: tpl['invalid_card'] = True
def semester_post_save(sender, instance, **kwargs): sem = instance shifts = sem.shift_set.filter( Q(when__lt=sem.start) | Q(when__gt=sem.end)) deleted_count = len(shifts) shifts.delete() weekdays = (5, 6) created_count = 0 for day in sem.date_range(): if day.weekday() in weekdays: continue for early_or_lunch_or_late in (0, 1, 2): obj, created = Shift.objects.get_or_create( semester=sem, span=early_or_lunch_or_late, when=day) if created: created_count += 1 logger = get_logger('baljan.semesters') logger.info('%s: %d/%d shifts added/deleted, signups=%s' % ( sem.name, created_count, deleted_count, sem.signup_possible)) # Create new shift combinations for job openings. from baljan import workdist sched = workdist.Scheduler(sem) try: sched.save() except workdist.Scheduler.Unsolvable: logger.warning('could not save shift combs for %r' % sem)
def signup_pre_save(sender, instance=None, **kwargs): if instance is None: return signup = instance signup_post(sender, signup, **kwargs) # Remove pending trade requests that, if accepted, would result in a user # being double-booked for a shift. trs_possible_doubles = TradeRequest.objects.filter( Q(wanted_signup__shift=signup.shift, offered_signup__user=signup.user) | Q(wanted_signup__user=signup.user, offered_signup__shift=signup.shift)) trs_possible_doubles.delete() if signup.tradable: get_logger('baljan.signups').info("%s saved (tradable)" % signup) else: get_logger('baljan.signups').info("%s saved (not tradable)" % signup) signup_notice_save(signup)
def traderequest_post_delete(sender, instance=None, **kwargs): if instance is None: return tr = instance # Mark other trade requests involving the wanted or offered sign-up as # denied and answered, so that notifications will be sent for them. Do not # mark requests where the requester and wanted shift is the same as this # one, to prevent sending unnecessary notifications. Save configurations for # sign-ups to be created and delete the current ones. If the request was # denied, there is no need to do anything besides sending the appropriate # notifications. if tr.accepted: answerer = tr.wanted_signup.user requester = tr.offered_signup.user logger = get_logger('baljan.trades') logger.info('%s accepted' % tr, trade_request=tr) TradeRequest.objects.filter( Q(wanted_signup=tr.wanted_signup) | Q(wanted_signup=tr.offered_signup) | Q(offered_signup=tr.wanted_signup) | Q(offered_signup=tr.offered_signup)).exclude( Q(pk=tr.pk) | Q(offered_signup__user=requester, wanted_signup=tr.wanted_signup)).update( accepted=False, answered=True) accepter_kwargs = { 'user': answerer, 'shift': tr.offered_signup.shift, } requester_kwargs = { 'user': requester, 'shift': tr.wanted_signup.shift, } tr.offered_signup.delete() tr.wanted_signup.delete() accepter_signup = ShiftSignup(**accepter_kwargs) accepter_signup.save() requester_signup = ShiftSignup(**requester_kwargs) requester_signup.save()
def job_opening_projector(request, semester_name): opening_log = get_logger('baljan.jobopening') tpl = {} tpl['semester'] = sem = baljan.models.Semester.objects.get(name__exact=semester_name) user = request.user sched = workdist.Scheduler(sem) pairs = sched.pairs_from_db() slots = _pair_matrix(pairs) tpl['now'] = now = datetime.now().strftime('%H:%M:%S') if request.is_ajax(): pair_info = [] for pair in pairs: pair_info.append({ 'label': pair.label, 'free': pair.is_free(), }) return HttpResponse(simplejson.dumps({'now': now, 'pairs': pair_info})) tpl['slots'] = slots return render_to_response('baljan/job_opening_projector.html', tpl, context_instance=RequestContext(request))
def traderequest_notice_delete(tr): if tr.answered: cancel = False if tr.wanted_signup.shift.when < date.today(): cancel = True if tr.offered_signup.shift.when < date.today(): cancel = True if cancel: logger = get_logger('baljan.trades') logger.info('not sending message about trade request deletion because the offered or wanted shift was in the past') return if tr.accepted: send_type = 'trade_request_accepted' else: send_type = 'trade_request_denied' requestor = tr.offered_signup.user notifications.send(send_type, requestor, wanted_shift=tr.wanted_signup.shift.name(), offered_shift=tr.offered_signup.shift.name(), ) else: pass # FIXME: notifications should be sent here as well
# -*- coding: utf-8 -*- from celery.decorators import task, periodic_task from celery.task.schedules import crontab from baljan.sounds import play_sound from baljan.util import get_logger from django.conf import settings from django.contrib.auth.models import User, Group from datetime import datetime from baljan import orders from baljan.card2user import Card2User from baljan import stats from django.core.cache import cache log = get_logger('baljan.tasks', with_sentry=False) try: from baljan.lcd import LCD, ADUNO, get_lcd lcd = get_lcd() except Exception, e: log.warning('no LCD support (%s)' % e) tasklog = get_logger('baljan.cardreader.tasks', with_sentry=False) user_finder = Card2User() def _do_prefetch(): tasklog.info('prefetching users for blipper, this can take some time') user_finder.prefetch_all() @task(ignore_result=True) def blipper_prefetch_users(): _do_prefetch()
# -*- coding: utf-8 -*- from optparse import make_option import os import readline import sys from django.conf import settings from django.contrib.auth.models import User, Permission, Group from django.core.management.base import BaseCommand, CommandError from django.utils.translation import ugettext as _ from django.db import transaction from baljan.models import Semester from baljan.util import get_logger, asciilize, random_string log = get_logger('baljan.commands.updateworkers') class Command(BaseCommand): args = 'SEMESTER' help = 'Set workers to everyone signed up for a shift in SEMESTER.' def handle(self, *args, **options): valid = True if not valid: raise CommandError('invalid config') if len(args) != 1: raise CommandError('usage: updateworkers SEMESTER') worker_group = Group.objects.get(name__exact=settings.WORKER_GROUP)
# -*- coding: utf-8 -*- from django.conf import settings from django.core.cache import cache from django.contrib.auth.models import User from django.utils.importlib import import_module from baljan.util import get_logger log = get_logger('baljan.card2user', with_sentry=False) class Cacher(object): prefix = 'baljan.card2user.cards' def _key(self, card_id): return "%s.%s" % (self.prefix, card_id) def store(self, card_id, user, silent=False): key = self._key(card_id) one_week = 604800 # seconds cache.set(key, user.id, one_week) if not silent: log.info('set cache of %s to %s' % (card_id, user)) return self def get(self, card_id): key = self._key(card_id) cached = cache.get(key) if cached is None: log.info('%s not in cache' % card_id) return None try:
# -*- coding: utf-8 -*- import os from subprocess import Popen, PIPE from django.conf import settings from baljan.util import get_logger log = get_logger('baljan.parport', with_sentry=False) PIN_GREEN = 2 PIN_RED = 3 PIN_BEEP = 4 PIN_LCD = 7 class ParPortError(Exception): pass class ParPort(object): def __init__(self): self.path = settings.PAR_PORT_PROG if os.path.isfile(self.path): pass else: msg = "%r does not exist" % self.path log.error(msg) def go(self, flags): """Blocking.""" # FIXME: sudo should not be needed! p = Popen(["/usr/bin/sudo", self.path] + flags, stdin=PIPE, stderr=PIPE, stdout=PIPE,
# -*- coding: utf-8 -*- from django.core.urlresolvers import reverse from django.core.mail import send_mail from django.utils.translation import ugettext_lazy from django.contrib.sites.models import Site from django.conf import settings from baljan.util import get_logger logger = get_logger('baljan.notifications') TITLE_TEMPLATES = { 'friend_request_denied': ugettext_lazy("""%(requestee)s denied your friend request"""), 'friend_request_accepted': ugettext_lazy("""You are now friends with %(requestee)s"""), 'friend_request_received': ugettext_lazy("""You have received a friend request from %(requestor)s"""), 'friend_request_canceled': ugettext_lazy("""The friend request from %(requestor)s was canceled"""), 'added_to_shift': ugettext_lazy("""You were signed up for %(shift)s"""), 'removed_from_shift': ugettext_lazy("""You were removed from %(shift)s"""), 'new_trade_request': ugettext_lazy("""%(requestor)s wants %(wanted_shift)s in exchange for %(offered_shift)s"""), 'trade_request_accepted': ugettext_lazy("""Your request to trade %(wanted_shift)s for %(offered_shift)s was accepted"""), 'trade_request_denied': ugettext_lazy("""Your request to trade %(wanted_shift)s for %(offered_shift)s was denied"""), }
# -*- coding: utf-8 -*- from django.core.management.base import BaseCommand, CommandError from django.conf import settings from baljan.tasks import test_play_all, SOUND_FUNCS_AND_DESCS from baljan.tasks import SOUND_FUNCS_AND_LIKELINESS from baljan.util import get_logger from django.utils.translation import ugettext as _ import os from time import sleep import readline from random import uniform, normalvariate log = get_logger('baljan.commands.demosound') def play_random(): rand = uniform(0, 1) for func, thres in SOUND_FUNCS_AND_LIKELINESS: if rand < thres: func.delay() break else: SOUND_FUNCS_AND_LIKELINESS[-1][0].delay() def sleep_random(): mean, var = 3, 5 rand = max(mean, abs(normalvariate(mean, var))) print "sleeping for %.2fs" % rand sleep(rand)
def oncallduty_post_delete(sender, instance=None, **kwargs): if instance is None: return signup_notice_delete(instance) get_logger('baljan.signups').info("%s deleted" % instance)
def semester_post_delete(sender, instance, **kwargs): if instance is None: return sem = instance logger = get_logger('baljan.semesters') logger.info('%s: deleted' % sem.name)
def admin_semester(request, name=None): if name is None: sem = baljan.models.Semester.objects.current() if sem is None: try: upcoming_sems = baljan.models.Semester.objects.upcoming() sem = upcoming_sems[0] except: pass else: sem = baljan.models.Semester.objects.by_name(name) user = request.user new_sem_form = baljan.forms.SemesterForm() if request.method == 'POST': if request.POST['task'] == 'new_semester': new_sem_form = baljan.forms.SemesterForm(request.POST) elif request.POST['task'] == 'edit_shifts': assert sem is not None raw_ids = request.POST['shift-ids'].strip() edit_shift_ids = [] if len(raw_ids): edit_shift_ids = [int(x) for x in raw_ids.split('|')] make = request.POST['make'] shifts_to_edit = baljan.models.Shift.objects.filter( id__in=edit_shift_ids).distinct() if make == 'normal': shifts_to_edit.update(exam_period=False, enabled=True) elif make == 'disabled': shifts_to_edit.update(exam_period=False, enabled=False) elif make == 'exam-period': shifts_to_edit.update(exam_period=True, enabled=True) elif make == 'none': pass else: get_logger('baljan.semesters').warning('unexpected task %r' % make) assert False if make != 'none': sem.save() # generates new shift combinations new_sem_failed = False if new_sem_form.is_bound: if new_sem_form.is_valid(): new_sem = new_sem_form.save() new_sem_url = reverse('baljan.views.admin_semester', args=(new_sem.name,) ) messages.add_message(request, messages.SUCCESS, _("%s was added successfully.") % new_sem.name ) return HttpResponseRedirect(new_sem_url) else: new_sem_failed = True tpl = {} tpl['semester'] = sem tpl['new_semester_form'] = new_sem_form tpl['semesters'] = baljan.models.Semester.objects.order_by('-start').all() tpl['admin_semester_base_url'] = reverse('baljan.views.admin_semester') tpl['new_semester_failed'] = new_sem_failed if sem: tpl['shifts'] = shifts = sem.shift_set.order_by('when', 'span') tpl['day_count'] = len(list(sem.date_range())) worker_shifts = shifts.exclude(enabled=False).exclude(span=1) tpl['worker_shift_count'] = worker_shifts.count() tpl['exam_period_count'] = worker_shifts.filter(exam_period=True).count() return render_to_response('baljan/admin_semester.html', tpl, context_instance=RequestContext(request))
# -*- coding: utf-8 -*- from django.conf import settings from django.contrib.auth.models import User, Group from baljan.util import get_logger log = get_logger('baljan.card2user.manualdb', with_sentry=False) class Finder(object): def search(self, card_id): try: user = User.objects.get(profile__card_id=card_id) log.info('found %s in local db' % user) return user except: log.info('owner of card %s could not be fetched' % card_id) return None
# -*- coding: utf-8 -*- from django.contrib.auth.models import User, Permission, Group from baljan.models import FriendRequest from django.db.models import Q from datetime import date from baljan.util import get_logger logger = get_logger('baljan.friends') def pending_between(usera, userb): if usera == userb: return None p = FriendRequest.objects.filter( Q(sent_by=usera) | Q(sent_by=userb), Q(sent_to=usera) | Q(sent_to=userb), answered_at__isnull=True) if p.count(): assert p.count() == 1 return p[0] return None def answer_to(frequest, accept): sent_by = frequest.sent_by.get_profile() sent_to = frequest.sent_to.get_profile() if accept: if sent_to in sent_by.friend_profiles.all(): pass else: sent_by.friend_profiles.add(sent_to)
# -*- coding: utf-8 -*- from django.conf import settings from django.contrib.auth.models import User, check_password from baljan.util import get_logger, get_or_create_user import ldap import re log = get_logger('baljan.ldap') def valid_username(username): return re.match('^[a-z]{2,5}[0-9]{3,3}$', username) is not None def exists_in_ldap(username): return fetch_user(username, bind=False, create=False) def search(username, password=None, bind=False): if not valid_username(username): log.error('invalid username %r' % username) return None base = "ou=people,dc=student,dc=liu,dc=se" uid_kw = "uid=%s" % username ldap_bind_str = "%s, %s" % (uid_kw, base) scope = ldap.SCOPE_SUBTREE ret = None try: l = ldap.initialize(settings.LDAP_SERVER) l.protocol_version = ldap.VERSION3 if bind:
# -*- coding: utf-8 -*- from baljan.models import Shift from baljan.util import year_and_week, get_logger from baljan.util import available_for_call_duty from baljan.util import initials, all_initials from baljan.grids import ShiftGrid from django.contrib.auth.models import User, Permission, Group log = get_logger('baljan.planning') class BoardWeek(object): """Board activities of a week. Used for editing people on call and such things. """ @staticmethod def current_week(): return BoardWeek(*year_and_week()) def __init__(self, year, week): self.shifts = Shift.objects.for_week(year, week) @staticmethod def dom_id(shift): daynum = int(shift.when.strftime('%w')) return "shift-%d-%d" % (daynum, shift.span) def dom_ids(self): return [BoardWeek.dom_id(sh) for sh in self.shifts] def oncall(self):
# -*- coding: utf-8 -*- from baljan.models import Order, OrderGood from baljan.models import OnCallDuty, Good from django.utils.translation import ugettext_lazy as _ from datetime import date, datetime from django.conf import settings from django.contrib.auth.models import User, Group from baljan.util import get_logger from baljan.pseudogroups import was_worker, was_board from dateutil.relativedelta import relativedelta log = get_logger('baljan.orders', with_sentry=False) prelog = get_logger('baljan.orders.pre', with_sentry=False) rebatelog = get_logger('baljan.orders.rebate', with_sentry=False) class Processed(object): default_reason = _("The order was processed.") def __init__(self, preorder, reason=None): self.free = preorder.free self.rebate = preorder.rebate self.preorder = preorder self.reason = self.default_reason if reason is None else reason def accepted(self): raise NotImplementedError() def create_order_and_update_balance(self): raise NotImplementedError()
from django.conf import settings from django.contrib.auth.models import User, Group import MySQLdb import MySQLdb.cursors from datetime import date, datetime from baljan.models import Profile, Semester, ShiftSignup, Shift, BoardPost from baljan.models import OnCallDuty, Good, Order from baljan.models import OldCoffeeCard, OldCoffeeCardSet from baljan.util import get_logger, get_or_create_user from baljan import pseudogroups from baljan import orders from dateutil.relativedelta import relativedelta import re from emailconfirmation.models import EmailAddress log = get_logger('baljan.migration', with_sentry=False) manual_board = [] class Import(object): def __init__(self): self._users = {} # Make sure that all needed settings and such are configured. settingfmt = 'OLD_SYSTEM_MYSQL_%s' valid = True errs = [] for setting in ( 'LOGIN', 'PASSWORD', 'DB',
def job_opening(request, semester_name): opening_log = get_logger('baljan.jobopening') tpl = {} tpl['semester'] = sem = baljan.models.Semester.objects.get(name__exact=semester_name) user = request.user found_user = None if request.method == 'POST': if request.is_ajax(): # find user searched_for = request.POST['liu_id'] valid_search = valid_username(searched_for) if valid_search: results = baljan.search.for_person(searched_for, use_cache=False) if len(results) == 1: found_user = results[0] if valid_search and found_user is None: # FIXME: User should not be created immediately. First we # should tell whether or not he exists, then the operator # may choose to import the user. if exists_in_ldap(searched_for): opening_log.info('%s found in LDAP' % searched_for) found_user = fetch_user(searched_for) else: opening_log.info('%s not found in LDAP' % searched_for) info = {} info['user'] = None info['msg'] = _('enter liu id') info['msg_class'] = 'pending' info['all_ok'] = False if found_user: info['user'] = { 'username': found_user.username, 'text': "%s (%s)" % ( found_user.get_full_name(), found_user.username ), 'phone': found_user.get_profile().mobile_phone, 'url': found_user.get_absolute_url(), } info['msg'] = _('OK') info['msg_class'] = 'saved' info['all_ok'] = True else: if valid_search: info['msg'] = _('liu id unfound') info['msg_class'] = 'invalid' return HttpResponse(simplejson.dumps(info)) else: # the user hit save, assign users to shifts shift_ids = [int(x) for x in request.POST['shift-ids'].split('|')] usernames = request.POST['user-ids'].split('|') phones = request.POST['phones'].split('|') # Update phone numbers. for uname, phone in zip(usernames, phones): try: to_update = baljan.models.Profile.objects.get( user__username__exact=uname ) to_update.mobile_phone = phone to_update.save() except: opening_log.error('invalid phone for %s: %r' % (uname, phone)) # Assign to shifts. shifts_to_save = baljan.models.Shift.objects.filter(pk__in=shift_ids) users_to_save = User.objects.filter(username__in=usernames) for shift_to_save in shifts_to_save: for user_to_save in users_to_save: signup, created = baljan.models.ShiftSignup.objects.get_or_create( user=user_to_save, shift=shift_to_save ) if created: opening_log.info('%r created' % signup) else: opening_log.info('%r already existed' % signup) sched = workdist.Scheduler(sem) pairs = sched.pairs_from_db() slots = _pair_matrix(pairs) pair_javascript = {} for pair in pairs: pair_javascript[pair.label] = { 'shifts': [unicode(sh.name()) for sh in pair.shifts], 'ids': [sh.pk for sh in pair.shifts], } tpl['slots'] = slots tpl['pair_javascript'] = simplejson.dumps(pair_javascript) tpl['pairs_free'] = pairs_free = len([p for p in pairs if p.is_free()]) tpl['pairs_taken'] = pairs_taken = len([p for p in pairs if p.is_taken()]) tpl['pairs_total'] = pairs_total = pairs_free + pairs_taken tpl['pairs_taken_percent'] = int(round(pairs_taken * 100.0 / pairs_total)) return render_to_response('baljan/job_opening.html', tpl, context_instance=RequestContext(request))
# -*- coding: utf-8 -*- from baljan.util import get_logger from django.conf import settings from threading import Lock from time import sleep import serial import string from baljan.parport import ParPort from datetime import datetime from celery.decorators import task log = get_logger('baljan.lcd', with_sentry=False) SEND_SLEEP_SECONDS = 0.0021 BYTE_CMD = chr(254) CMD_ROWS = [ BYTE_CMD + chr(128), # move cursor to 0th col in row 0 BYTE_CMD + chr(192), # move cursor to 0th col in row 1 ] CMD_CLEAR = BYTE_CMD + chr(1) ADUNO = u'\\(o_O)/' COLS = 16 ROWS = len(CMD_ROWS) def encode(msg): """`msg` will be converted to a regular string if it is of unicode type. """ # TODO: make complete
# -*- coding: utf-8 -*- from baljan.models import BalanceCode, OldCoffeeCard from datetime import datetime from baljan.util import get_logger from datetime import datetime from django.conf import settings log = get_logger('baljan.credits') class CreditsError(Exception): pass class BadCode(CreditsError): pass def used_by(user, old_card=False): if old_card: return OldCoffeeCard.objects.filter( user=user, ).order_by('-id') else: return BalanceCode.objects.filter( used_by=user, ).order_by('-used_at', '-id') def get_unused_code(entered_code, old_card=False): """Can return either an `OldCoffeeCard` or a `BalanceCode` depending on the value of the `old_card` parameter.""" now = datetime.now() try:
# -*- coding: utf-8 -*- import os from subprocess import Popen, PIPE from django.conf import settings from baljan.util import get_logger log = get_logger('baljan.sounds', with_sentry=False) class SoundError(Exception): pass class NoSuchFile(SoundError): pass class CouldNotPlay(SoundError): pass class Sound(object): def __init__(self, basename): self.path = os.path.join(settings.SOUND_DIR, basename) if os.path.isfile(self.path): pass else: msg = "%r does not exist" % self.path log.error(msg) raise NoSuchFile(msg) def play(self): """Blocking.""" p = Popen([settings.SOUND_CMD, self.path], stdin=PIPE, stderr=PIPE,
# -*- coding: utf-8 -*- from django.contrib.auth.models import User, Permission, Group from django.conf import settings from django.utils.translation import ugettext as _ from baljan.models import Semester, BoardPost from baljan.util import get_logger from django.db.models import Q from datetime import date, datetime log = get_logger('baljan.pseudogroups') def real_only(): return Group.objects.all().exclude(name__startswith='_') class PseudoGroupError(Exception): pass def _semname(is_spring, year): pre = 'HT' if is_spring: pre = 'VT' return "%s%d" % (pre, year) def manual_group_from_semester(base_group, sem): return manual_group(base_group, sem.spring(), sem.year()) def manual_group(base_group, is_spring, year): if base_group.name == settings.BOARD_GROUP: