예제 #1
0
def automatic(request, dist_type, distribute_random=1, automotive_preference=1):
    """
    After automatic distribution, this pages shows how good the automatic distribution is. At this point a type3staff
     member can choose to apply the distributions. Later, this distribution can be edited using manual distributions.

    :param request:
    :param dist_type: which type automatic distribution is used.
    :param distribute_random: Whether to distribute leftover students to random projects
    :param automotive_preference: Distribute automotive students first to automotive people
    :return:
    """
    if get_timephase_number() < 4 or get_timephase_number() > 5:  # 4 or 5
        raise PermissionDenied('Distribution is not possible in this timephase')

    if int(dist_type) == 1:
        typename = 'calculated by student'
    elif int(dist_type) == 2:
        typename = 'calculated by project'
    else:
        raise PermissionDenied("Invalid type")

    if distribute_random not in [0, 1]:
        raise PermissionDenied("Invalid option type random")
    if automotive_preference not in [0, 1]:
        raise PermissionDenied("Invalid option type automotive")

    dists = []  # list to store actual user and proposal objects, instead of just ID's like distobjs.
    if request.method == 'POST':
        jsondata = request.POST.get('jsondata', None)
        if jsondata is None:
            return render(request, 'base.html', {'Message': 'Invalid POST data'})
        distobjs = json.loads(jsondata)  # json blob with all dists with studentID projectID and preference
        for obj in distobjs:
            dists.append({
                'student': User.objects.get(pk=obj['StudentID']),
                'proposal': get_all_proposals().get(pk=obj['ProjectID']),
                'preference': obj['Preference'],
            })

        form = ConfirmForm(request.POST)
        if form.is_valid():
            # delete all old distributions of this timeslot
            Distribution.objects.filter(TimeSlot=get_timeslot()).delete()
            # save the stuff
            for dist in dists:
                dstdbobj = Distribution()
                dstdbobj.Student = dist['student']
                dstdbobj.Proposal = dist['proposal']
                dstdbobj.TimeSlot = get_timeslot()
                if dist['preference'] > 0:
                    try:
                        dstdbobj.Application = \
                            Application.objects.filter(Q(Student=dist['student']) &
                                                       Q(Proposal=dist['proposal']) &
                                                       Q(Priority=dist['preference']) &
                                                       Q(Proposal__TimeSlot=get_timeslot()))[0]
                    except Application.DoesNotExist:
                        dstdbobj.Application = None
                else:
                    dstdbobj.Application = None
                dstdbobj.save()

            return render(request, 'base.html', {
                'Message': 'Distributions saved!',
                'return': 'distributions:SupportListApplicationsDistributions',
            })

    else:
        # Handle the most common errors beforehand with a nice message
        error_list = ''
        stds = get_all_students().filter(personal_proposal__isnull=False, personal_proposal__TimeSlot=get_timeslot()).distinct().prefetch_related('personal_proposal')
        for u in stds:  # all private students
            ps = u.personal_proposal.filter(TimeSlot=get_timeslot())
            if ps.count() > 1:  # more than one private proposal.
                error_list += "<li>User {} has multiple private proposals ({}). Please resolve this!</li>" \
                    .format(u, print_list(ps))
            if ps.first().Status != 4:
                error_list += "<li>User {} has private proposals which is not yet public. Please upgrade proposal {} to public!</li>" \
                    .format(u, ps.first())
        if error_list:
            return render(request, 'base.html', {
                'Message': '<h1>Automatic distribution cannot start</h1><p>The following error(s) occurred:</p><ul>{}</ul>'
                          .format(error_list),
                'return': 'distributions:SupportListApplicationsDistributions',
            })

        form = ConfirmForm()
        # run the algorithms
        # catch any remaining errors of the algorithm with a broad exception exception.
        try:
            if int(dist_type) == 1:  # from student
                distobjs = distribution.calculate_1_from_student(
                    distribute_random=distribute_random,
                    automotive_preference=automotive_preference)
            elif int(dist_type) == 2:  # from project
                distobjs = distribution.calculate_2_from_project(
                    distribute_random=distribute_random,
                    automotive_preference=automotive_preference)
        # invalid types are catched at the begin of the function
        except Exception as e:
            return render(request, "base.html", {
                'Message': '<h1>Automatic distribution cannot start</h1><p>The following error(s) occurred:</p>{}'
                          .format(e)})

    # convert to django models from db
    # and make scatter chart data
    scatter = []
    undistributed = list(get_all_students())  # get all students and remove all distributed students later.
    for obj in distobjs:  # distobjs is saved as json blob in the view, to use later on for distributions.
        student = get_all_students().get(pk=obj.StudentID)
        undistributed.remove(student)  # remove distributed student from undistributed students list.
        scatter.append({  # data for scatterplot ECTS vs Preference
            'x': student.usermeta.ECTS,
            'y': obj.Preference,
        })
        try:
            proposal = get_all_proposals().get(pk=obj.ProjectID)
        except Proposal.DoesNotExist:
            raise Exception("Proposal id {} cannot be found in all_proposals!".format(obj.ProjectID))
        dists.append({
            'student': student,
            'proposal': proposal,
            'preference': obj.Preference,
        })
    # show undistributed students also in the table. (Not added to json blob)
    for obj in undistributed:
        dists.append({
            'student': obj,
            'proposal': None,
            'preference': 'Not distributed. ' + ('With' if obj.applications.filter(Proposal__TimeSlot=get_timeslot()).exists() else 'No') + ' applications.',
        })

    cohorts = distribution.get_cohorts()

    # make headers for table and find cohort for each student.
    columns = ['Total']
    # calculate stats per cohort
    prefs = {  # first column with total
        'Total': [obj['preference'] for obj in dists if obj['proposal'] is not None],
    }
    for c in cohorts:  # all next columns in table for each cohort. First column is totals.
        columns.append(c)
        prefs[c] = []
        for obj in dists:
            if obj['proposal'] is not None:  # do not count not-distributed students in the stats.
                if obj['student'].usermeta.Cohort == int(c):
                    prefs[c].append(obj['preference'])  # list of all individual preferences in this cohort

    # make a table
    table = []
    pref_options = list(range(-1, settings.MAX_NUM_APPLICATIONS + 1))  # all options for preference.
    for pref in pref_options:  # each preference, each row.
        # set first column
        if pref == -1:  # random distributed students.
            this_row = ['Random']
        elif pref == 0:
            this_row = ['Private']  # private proposals
        else:
            this_row = ['#' + str(pref), ]  # application preference
        # set other columns
        for c in columns:  # add columns to the row.
            num = prefs[c].count(pref)
            try:
                this_row.append('{}% ({})'.format(round(num / len(prefs[c]) * 100), num))
            except ZeroDivisionError:
                this_row.append('{}% ({})'.format(0, 0))

        # add row the the table
        table.append(this_row)
    # one but last row with totals.
    this_row = ['Total Distributed']
    for c in columns:
        this_row.append(len(prefs[c]))
    table.append(this_row)

    # last row with undistributed.
    this_row = ['Not Distributed', len(undistributed)]
    for c in columns[1:]:  # skip total column, is already added.
        this_row.append(len([u for u in undistributed if u.usermeta.Cohort == c]))
    table.append(this_row)

    # show the tables for testing.
    if settings.TESTING:
        return columns, table, dists

    data = [obj.as_json() for obj in distobjs]
    return render(request, 'distributions/automatic_distribute.html', {
        'typename': typename,
        'distributions': dists,
        'form': form,
        'stats': table,
        'stats_header': columns,
        'scatter': scatter,
        'jsondata': json.dumps(data),
        'distribute_random': distribute_random,
        'automotive_preference': automotive_preference,
    })
예제 #2
0
def mailing(request, pk=None):
    """
    Mailing list to mail users.

    :param request:
    :param pk: optional key of a mailing template
    :return:
    """
    if request.method == 'POST':
        form = ChooseMailingList(
            request.POST,
            staff_options=mail_staff_options,
            student_options=mail_student_options,
        )
        if form.is_valid():
            recipients_staff = set()
            recipients_students = set()
            # staff
            if form.cleaned_data['SaveTemplate']:
                t = MailTemplate(
                    RecipientsStaff=json.dumps(form.cleaned_data['Staff']),
                    RecipientsStudents=json.dumps(
                        form.cleaned_data['Students']),
                    Message=form.cleaned_data['Message'],
                    Subject=form.cleaned_data['Subject'],
                )
                t.save()

            ts = form.cleaned_data['TimeSlot']
            for s in form.cleaned_data['Staff']:
                try:
                    # staff selected by group
                    recipients_staff.update(
                        Group.objects.get(name=s).user_set.all())
                    if s not in [
                            'type3staff', 'type4staff', 'type5staff',
                            'type6staff'
                    ]:
                        # if user group is not a support type, mail only users with project in this year.
                        for staff in list(recipients_staff):
                            if not staff.proposalsresponsible.filter(
                                    TimeSlot=ts).exists(
                                    ) and not staff.proposals.filter(
                                        TimeSlot=ts).exists():
                                # user has no project in the selected timeslots.
                                recipients_staff.remove(staff)
                except Group.DoesNotExist:
                    # not a group object, staff selected by custom options.
                    projs = get_all_proposals(old=True).filter(TimeSlot=ts)
                    if s == 'staffnonfinishedproj':
                        for proj in projs.filter(Status__lt=3).distinct():
                            recipients_staff.add(proj.ResponsibleStaff)
                            recipients_staff.update(proj.Assistants.all())
                    elif s == 'distributedstaff':
                        for proj in projs.filter(
                                distributions__isnull=False).distinct():
                            recipients_staff.add(proj.ResponsibleStaff)
                            recipients_staff.update(proj.Assistants.all())
                    elif s == 'staffnostudents':
                        for proj in projs.filter(
                                distributions__isnull=True).distinct():
                            recipients_staff.add(proj.ResponsibleStaff)
                            recipients_staff.update(proj.Assistants.all())
                    elif s == 'assessors':
                        dists = Distribution.objects.filter(
                            TimeSlot=ts).distinct()
                        for d in dists:
                            try:
                                recipients_staff.update(
                                    d.presentationtimeslot.Presentations.
                                    Assessors.all())
                            except PresentationTimeSlot.DoesNotExist:
                                continue
                        recipients_staff.update(
                            User.objects.filter(
                                tracks__isnull=False))  # add trackheads
            # students
            students = User.objects.filter(
                Q(usermeta__TimeSlot=ts) & Q(usermeta__EnrolledBEP=True)
                & Q(groups=None))
            for s in form.cleaned_data['Students']:
                if s == 'all':
                    recipients_students.update(students)
                elif s == '10ectsstd':
                    recipients_students.update(
                        students.filter(usermeta__EnrolledExt=False))
                elif s == '15ectsstd':
                    recipients_students.update(
                        students.filter(usermeta__EnrolledExt=True))
                elif s == 'distributedstd':
                    recipients_students.update(
                        students.filter(distributions__isnull=False,
                                        distributions__TimeSlot=ts).distinct())

            # always send copy to admins
            for user in User.objects.filter(is_superuser=True):
                recipients_staff.add(user)
            # always send copy to self
            if request.user not in recipients_students or \
                    request.user not in recipients_staff:
                recipients_staff.update([request.user])

            mailing_obj = Mailing(
                Subject=form.cleaned_data['Subject'],
                Message=form.cleaned_data['Message'],
            )
            mailing_obj.save()
            mailing_obj.RecipientsStaff.set(recipients_staff)
            mailing_obj.RecipientsStudents.set(recipients_students)
            context = {
                'form': ConfirmForm(initial={'confirm': True}),
                'template': form.cleaned_data['SaveTemplate'],
                'mailing': mailing_obj,
            }
            return render(request,
                          "support/email_confirm.html",
                          context=context)
    else:
        initial = None
        if pk:
            template = get_object_or_404(MailTemplate, pk=pk)
            initial = {
                'Message': template.Message,
                'Subject': template.Subject,
                'Staff': json.loads(template.RecipientsStaff),
                'Students': json.loads(template.RecipientsStudents),
            }
        form = ChooseMailingList(initial=initial,
                                 staff_options=mail_staff_options,
                                 student_options=mail_student_options)
    return render(
        request, "GenericForm.html", {
            "form": form,
            "formtitle": "Send mailing list",
            "buttontext": "Go to confirm",
        })
예제 #3
0
def mail_distributions(request):
    """
    Mail all distributions to affected users

    :param request:
    :return:
    """
    if get_timephase_number() < 4 or get_timephase_number() > 5:
        # mailing is possible in phase 4 or 5
        raise PermissionDenied('Mailing distributions is not possible in this timephase')

    if request.method == 'POST':
        form = ConfirmForm(request.POST)
        if form.is_valid():
            mails = []
            ts = get_timeslot()
            # iterate through projects, put students directly in the mail list
            for prop in get_all_proposals().filter(Q(distributions__isnull=False)):
                for dist in prop.distributions.filter(TimeSlot=ts):
                    mails.append({
                        'template': 'email/studentdistribution.html',
                        'email': dist.Student.email,
                        'subject': 'distribution',
                        'context': {
                            'project': prop,
                            'student': dist.Student,
                        }
                    })

            # iterate through all assistants and responsible
            for usr in get_all_staff().filter(Q(groups__name='type1staff') | Q(groups__name='type2staff')):
                if usr.proposals.filter(TimeSlot=get_timeslot()).exists():
                    mails.append({
                        'template': 'email/assistantdistribution.html',
                        'email': usr.email,
                        'subject': 'distribution',
                        'context': {
                            'supervisor': usr,
                            'projects': usr.proposals.filter(TimeSlot=get_timeslot()).distinct(),
                        }
                    })
                if usr.proposalsresponsible.filter(TimeSlot=get_timeslot()).exists():
                    mails.append({
                        'template': 'email/supervisordistribution.html',
                        'email': usr.email,
                        'subject': 'distribution',
                        'context': {
                            'supervisor': usr,
                            'projects': usr.proposalsresponsible.filter(TimeSlot=get_timeslot()).distinct(),
                        }
                    })

            # UNCOMMENT THIS TO MAIL NOT_DISTRIBUTED STUDENTS WITH 'action required'
            # for usr in get_all_students().filter(distributions__isnull=True):
            #     mails.append({
            #         'template': 'email/studentnodistribution.html',
            #         'email': usr.email,
            #         'subject': 'action required',
            #         'context': {
            #             'student': usr,
            #         }
            #     })
            EmailThreadTemplate(mails).start()

            return render(request, 'support/email_progress.html')
    else:
        form = ConfirmForm()

    return render(request, 'GenericForm.html', {
        'form': form,
        'formtitle': 'Confirm mailing distributions',
        'buttontext': 'Confirm'
    })