def assign(request, pk):
    """
    Assign all distributed students to one of the prv groups.

    :param request:
    :param pk:
    :return:
    """
    filetype = get_object_or_404(FileType, pk=pk)
    if request.method == 'POST':
        form = ConfirmForm(request.POST)
        if form.is_valid():
            if filetype.groups.all().aggregate(
                    Sum('Max'))['Max__sum'] < get_all_students().count():
                return render(
                    request, 'base.html', {
                        'Message':
                        'Groups capacity not sufficient. Groups are not changed.',
                        'return': 'professionalskills:listgroups',
                        'returnget': filetype.id
                    })
            for group in filetype.groups.all():
                group.Members.clear()
                group.save()
            students = list(get_all_students())
            totalstudents = len(students)
            random.shuffle(students)
            groups = list(filetype.groups.all())
            totaldistributed = 0
            while totaldistributed < totalstudents:
                for g in [g for g in groups if g.Members.count() < g.Max]:
                    try:
                        g.Members.add(students.pop(0))
                        totaldistributed += 1
                    except IndexError:
                        break
            for g in groups:
                g.save()

            return render(
                request, 'base.html', {
                    'Message': 'Students divided over the groups.',
                    'return': 'professionalskills:listgroups',
                    'returnget': filetype.id,
                })
    else:
        form = ConfirmForm()

    return render(
        request, 'GenericForm.html', {
            'form': form,
            'formtitle':
            'Confirm reshuffling students for {}'.format(filetype),
            'buttontext': 'Confirm'
        })
Beispiel #2
0
def distribute_personal(distribution_proposals, students_done=[]):
    """
    Distribute all students with a private proposal to the private proposal.

    :param distribution_proposals: List of distributions
    :param students_done: list of distributed students
    :return:
    """
    stds = get_all_students().filter(
        personal_proposal__isnull=False,
        personal_proposal__TimeSlot=get_timeslot()).distinct(
        ).prefetch_related('personal_proposal')
    for s in stds:
        try:
            p = s.personal_proposal.filter(TimeSlot=get_timeslot()).get()
        except MultipleObjectsReturned:
            raise Exception(
                "User {} has multiple private proposals ({}). Please resolve this!"
                .format(
                    s,
                    print_list(
                        s.personal_proposal.filter(
                            TimeSlot=get_timeslot()).all())))
        if p.TimeSlot != get_timeslot():
            raise Exception(
                "User {} of this timeslot has private proposal {} of other timeslot {}"
                .format(s, p, p.TimeSlot))
        distribution_proposals.append(DistributionProposal(s.id, p.id, 0))
        students_done.append(s)
    return [distribution_proposals, students_done]
Beispiel #3
0
def manual(request):
    """
    Support page to distribute students manually to projects. Uses ajax calls to change distributions.
    Same table included as in list appls/dists

    :param request:
    """
    if get_timephase_number() < 4 or get_timephase_number() > 6:
        raise PermissionDenied('Distribution is not possible in this timephase')

    props = get_all_proposals().filter(Q(Status__exact=4)) \
        .select_related('ResponsibleStaff__usermeta', 'Track', 'TimeSlot') \
        .prefetch_related('Assistants__usermeta', 'Private__usermeta', 'applications__Student__usermeta',
                          'distributions__Student__usermeta')
    # includes students without applications.
    # also show undistributed in phase 6
    studs = get_all_students(undistributed=True).exclude(distributions__TimeSlot=get_timeslot()) \
        .select_related('usermeta') \
        .prefetch_related('applications__Proposal').distinct()
    dists = Distribution.objects.filter(TimeSlot=get_timeslot()) \
        .select_related('Student__usermeta', 'Proposal', 'Application__Student__usermeta')
    return render(request, 'distributions/manual_distribute.html', {'proposals': props,
                                                                    'undistributedStudents': studs,
                                                                    'distributions': dists,
                                                                    'hide_sidebar': True})
Beispiel #4
0
def api_undistribute(request):
    """
    AJAX call from manual distribute to undistribute

    :param request:
    :return:
    """
    if get_timephase_number() < 4 or get_timephase_number() > 5:
        # Not in phase 6, because projects already started.
        raise PermissionDenied('Undistribution is not possible in this timephase')

    if request.method == 'POST':
        try:
            student = get_all_students(undistributed=True).get(pk=request.POST['student'])
        except User.DoesNotExist:
            return JsonResponse({'type': 'warning', 'txt': warningString + ' (User cannot be found)'})
        try:
            dist = student.distributions.get(TimeSlot=get_timeslot())
        except Distribution.DoesNotExist:
            return JsonResponse({'type': 'warning', 'txt': warningString + ' (Distribution cannot be found)'})
        try:
            n = dist.delete()
            if n[0] == 1:
                return JsonResponse(
                    {'type': 'success', 'txt': 'Undistributed Student ' + dist.Student.usermeta.get_nice_name()})
            else:
                return JsonResponse({'type': 'warning', 'txt': warningString + ' (distributions not deleted)'})
        except Exception as e:
            return JsonResponse({'type': 'warning', 'txt': warningString, 'exception': str(e)})
    else:
        raise PermissionDenied('You don\'t know what you\'re doing!')
def list_second_choice(request):
    """
    list all students with a random distribution, without project and all non-full projects

    :param request:
    :return:
    """

    props = get_all_proposals().filter(
        Status=4, Private__isnull=True).distinct().annotate(
            num_distr=Count('distributions')).filter(
                TimeSlot=get_timeslot(),
                num_distr__lt=F('NumStudentsMax')).order_by('Title')
    prop_obj = [[prop, get_share_link(prop.pk)] for prop in props]
    no_dist = get_all_students(undistributed=True).filter(
        distributions__isnull=True, applications__isnull=False).distinct()
    # filter students in this year with only applications in other year
    no_dist = [
        s for s in no_dist
        if s.applications.filter(Proposal__TimeSlot=get_timeslot()).exists()
    ]

    return render(
        request, 'distributions/list_second_choice.html', {
            'distributions':
            Distribution.objects.filter(
                TimeSlot=get_timeslot(),
                Application__isnull=True,
                Proposal__Private__isnull=True).order_by('Student'),
            'no_dist':
            no_dist,
            'proposals':
            prop_obj,
        })
def get_valid_students():
    """
    All students with applications, with personal, in this timeslot.
    Personal proposals are distributed as first, so personal prop students will not get to the other distribution.

    :return: list of user objects.
    """
    return get_all_students().filter(applications__isnull=False).filter(applications__Proposal__TimeSlot=get_timeslot()).distinct()
Beispiel #7
0
def visitorsMenu(request):
    """
    List of all students, click on the student to view the proposals that he/she visited.

    :param request:
    :return:
    """

    return render(request, 'godpowers/visitorsmenu.html',
                  {'students': get_all_students()})
Beispiel #8
0
def get_valid_students():
    """
    All students with applications, without personal, in this timeslot.

    :return: list of user objects.
    """
    # TODO personal_proposal should be filtered by timeslot
    return get_all_students().filter(
        personal_proposal__isnull=True, applications__isnull=False).filter(
            applications__Proposal__TimeSlot=get_timeslot()).distinct()
Beispiel #9
0
def get_cohorts():
    """
    Get all cohorts that have students in this year.

    :return:
    """
    cohorts = []
    for s in get_all_students().select_related('usermeta'):
        c = s.usermeta.Cohort
        if c is not None:
            if c not in cohorts:
                cohorts.append(c)
    cohorts.sort(reverse=True)

    return cohorts
Beispiel #10
0
def api_redistribute(request):
    """
    AJAX call from manual distribute to change a distribution

    :param request:
    :return:
    """
    if get_timephase_number() < 4 or get_timephase_number() > 6:
        raise PermissionDenied('Distribution is not possible in this timephase')

    if request.method == 'POST':
        try:
            student = get_all_students(undistributed=True).get(pk=request.POST['student'])
        except User.DoesNotExist:
            return JsonResponse({'type': 'warning', 'txt': warningString + ' (User cannot be found)'})
        try:
            dist = student.distributions.get(TimeSlot=get_timeslot())
        except Distribution.DoesNotExist:
            return JsonResponse({'type': 'warning', 'txt': warningString + ' (Distribution cannot be found)'})
        try:
            # change Proposal
            dist.Proposal = get_cached_project(request.POST['propTo'])
            # change Application if user has Application
            try:
                dist.Application = get_all_applications(dist.Student).get(Proposal=dist.Proposal)
                appl_prio = dist.Application.Priority
            except Application.DoesNotExist:
                dist.Application = None
                appl_prio = -1
            dist.full_clean()
            dist.save()
        except Exception as e:
            return JsonResponse({'type': 'warning', 'txt': warningString, 'exception': str(e)})
        return JsonResponse(
            {'type': 'success', 'txt': 'Changed distributed Student ' + dist.Student.usermeta.get_nice_name() +
                                       ' to Proposal ' + dist.Proposal.Title, 'prio': appl_prio})
    else:
        raise PermissionDenied("You don't know what you're doing!")
Beispiel #11
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,
    })