コード例 #1
0
def copy_timephases(request):
    """
    Copy all timephases of one timeslot to the other timeslot

    :param request:
    :return:
    """
    if request.method == 'POST':
        form = TimePhaseCopyForm(request.POST)
        if form.is_valid():
            from_date = form.cleaned_data['ts_from'].Begin
            to_date = form.cleaned_data['ts_to'].Begin
            diff = to_date - from_date
            copied = []
            for tp in form.cleaned_data['ts_from'].timephases.all().order_by(
                    "Description"):
                try:
                    tp.TimeSlot = form.cleaned_data['ts_to']
                    tp.id = None  # to make a copy.
                    tp.Begin = tp.Begin + diff
                    tp.End = tp.End + diff
                    if hasattr(tp, 'CountDownEnd'):
                        tp.CountDownEnd = tp.CountDownEnd + diff
                    tp.full_clean()
                    tp.save()
                    copied.append(tp.Description)
                except ValidationError as e:  # form save error, invalid dates
                    return render(
                        request, 'base.html', {
                            'Message':
                            'TimePhases {} saved. Timephase {} could not be saved because of {}'
                            .format(print_list(copied), tp.Description,
                                    print_list(e.messages)),
                            'return':
                            'timeline:list_timeslots',
                        })
            return render(
                request, 'base.html', {
                    'Message':
                    'TimePhases {} saved! Please check and correct their Begin and End times manually.'
                    .format(print_list(copied)),
                    'return':
                    'timeline:list_timeslots',
                })

    else:
        form = TimePhaseCopyForm()
    return render(request, 'GenericForm.html', {
        'form': form,
        'formtitle': 'Copy TimePhase',
        'buttontext': 'Copy',
    })
コード例 #2
0
def edit_user_groups(request, pk):
    """
    Change the groups of a given user.

    :param request:
    :param pk: user id
    :return:
    """
    usr = get_object_or_404(User, pk=pk)
    if not usr.groups.exists():
        if not usr.is_superuser:
            raise PermissionDenied(
                "This user is a student. Students cannot have groups.")
    if get_grouptype("2u") in usr.groups.all():
        raise PermissionDenied(
            "This user is not yet verified. Please verify first in the user list."
        )

    if request.method == "POST":
        form = UserGroupsForm(request.POST, instance=usr)
        if form.is_valid():
            if form.has_changed():
                # call print list here to force query execute
                old = print_list(usr.groups.all().values_list('name',
                                                              flat=True))
                form.save()
                new = print_list(usr.groups.all().values_list('name',
                                                              flat=True))
                send_mail("user groups changed",
                          "email/user_groups_changed.html", {
                              'oldgroups': old,
                              'newgroups': new,
                              'user': usr
                          }, usr.email)
                return render(
                    request, 'base.html', {
                        'Message': 'User groups saved!',
                        'return': 'support:listusers',
                    })
            return render(request, 'base.html', {
                'Message': 'No changes made.',
                'return': 'support:listusers',
            })
    else:
        form = UserGroupsForm(instance=usr)
    return render(request, 'support/user_groups_form.html', {
        'formtitle': 'Set user groups for {}'.format(usr.username),
        'form': form,
    })
コード例 #3
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]
コード例 #4
0
 def clean(self):
     if self.File:
         if get_ext(self.File.name
                    ) not in settings.ALLOWED_PROJECT_ATTACHMENTS:
             raise ValidationError(
                 'This file type is not allowed. Allowed types: ' +
                 print_list(settings.ALLOWED_PROJECT_ATTACHMENTS))
コード例 #5
0
 def clean_File(self):
     file = clean_file_default(self)
     if get_ext(file.name) not in self.filetype.get_allowed_extensions():
         raise ValidationError(
             'This file extension is not allowed. Allowed extensions: ' +
             print_list(self.filetype.get_allowed_extensions()))
     return file
コード例 #6
0
def list_group_projects(request, timeslot=None):
    """
    List all proposals of a group.

    :param request:
    :param timeslot: timeslot to view.l
    :return:
    """
    if timeslot:
        ts = get_object_or_404(TimeSlot, pk=timeslot)
        projects = get_all_projects(old=True).filter(TimeSlot=ts)
    else:
        ts = None
        projects = get_all_projects(old=True).filter(
            TimeSlot=None)  # proposals of future timeslot

    projects = prefetch(
        projects.filter(Group__Administrators=request.user).distinct())
    return render(
        request, 'proposals/list_projects_custom.html', {
            'hide_sidebar':
            True,
            'projects':
            projects,
            'title':
            'Proposals of {}'.format(
                print_list(
                    request.user.administratoredgroups.all().values_list(
                        'Group__ShortName', flat=True))),
            'timeslots':
            get_recent_timeslots(),
            'timeslot':
            ts,
        })
コード例 #7
0
 def clean(self):
     self.Caption = clean_text(self.Caption)
     if self.File:
         if get_ext(self.File.name) not in settings.ALLOWED_PUBLIC_FILES:
             raise ValidationError(
                 'This file type is not allowed. Allowed types: ' +
                 print_list(settings.ALLOWED_PUBLIC_FILES))
コード例 #8
0
def clean_publicfile_default(self):
    file = clean_file_default(self)
    if get_ext(file.name) not in settings.ALLOWED_PUBLIC_FILES:
        raise ValidationError(
            'This file type is not allowed. Allowed types: ' +
            print_list(settings.ALLOWED_PUBLIC_FILES))
    return file
コード例 #9
0
def list_track(request, timeslot=None):
    """
    List all proposals of the track that the user is head of.

    :param request:
    :param timeslot: Time slot to show projects from
    :return:
    """
    if not Track.objects.filter(Head=request.user).exists():
        raise PermissionDenied("This page is only for track heads.")
    tracks = Track.objects.filter(Head__id=request.user.id)

    if timeslot:
        ts = get_object_or_404(TimeSlot, pk=timeslot)
        projects = get_all_projects(old=True).filter(TimeSlot=ts)
    else:
        ts = None
        projects = get_all_projects(old=True).filter(TimeSlot=None)  # proposals of future timeslot
    projects = projects.filter(Track__in=tracks)
    projects = prefetch(projects)
    return render(request, "proposals/list_projects_custom.html", {
        'projects': projects,
        'favorite_projects': get_favorites(request.user),
        "title": "Proposals of track {}".format(print_list(tracks)),
        'timeslots': get_recent_timeslots(),
        'timeslot': ts,
    })
コード例 #10
0
 def clean(self):
     self.Caption = clean_text(self.Caption)
     if self.File:
         if get_ext(
                 self.File.name) not in self.Type.get_allowed_extensions():
             raise ValidationError(
                 'This file type is not allowed. Allowed types: ' +
                 print_list(self.Type.get_allowed_extensions()))
コード例 #11
0
def wizard_step2_edit(request, kind):
    """
    Step 2 of the planning of the presentations for the projects.
    In this step the rooms for the presentations are set. Rooms have just a name. All used rooms have to be supplied.

    :param request:
    :param kind: Add or edit.
    :return:
    """
    ts = get_timeslot()
    if not hasattr(ts, 'presentationoptions'):
        return render(
            request, "base.html", {
                "Message":
                "There are no presentation options yet, please <a class='button success' href='"
                + reverse("presentations:presentationswizardstep1") +
                "'>go back to step 1</a>"
            })

    if kind == 'add':
        qs = Room.objects.none()
        ex = 10
    elif kind == 'edit':
        qs = Room.objects.all()
        ex = 0
    else:
        raise PermissionDenied('Wrong kind, choose add or edit.')

    form_set = modelformset_factory(Room,
                                    form=PresentationRoomForm,
                                    can_delete=kind == 'edit',
                                    extra=ex)
    formset = form_set(queryset=qs)
    if request.method == 'POST':
        formset = form_set(request.POST)
        if formset.is_valid():
            try:
                formset.save()
            except ProtectedError as e:
                raise PermissionDenied(
                    'Room can not be deleted, as other objects depend on it. Please remove the others first. Depending objects: {}'
                    .format(print_list(e.protected_objects)))
            return render(
                request, "base.html", {
                    "Message": "Rooms saved!",
                    "return": "presentations:presentationswizardstep2"
                })
    return render(
        request, 'GenericForm.html', {
            'formset':
            formset,
            'formtitle':
            "Presentations step 2; {} rooms for presentations & assessments".
            format(kind.capitalize()),
            'buttontext':
            'Save'
        })
コード例 #12
0
def osirisToMeta(request):
    write_errors = []
    try:
        data, log = read_osiris_xlsx()
    except:
        return render(
            request, 'base.html', {
                'Message':
                'Retrieving Osirisdata failed. Please upload a valid file.',
                'return': 'index:index',
            })

    if request.method == 'POST':
        count = 0
        form = ConfirmForm(request.POST)
        if form.is_valid():
            for p in data:
                try:
                    user = User.objects.get(email=p.email)
                except User.DoesNotExist:
                    write_errors.append('User {} skipped'.format(p.email))
                    continue
                try:
                    meta = user.usermeta
                except UserMeta.DoesNotExist:
                    meta = UserMeta()
                    user.usermeta = meta
                    meta.save()
                    user.save()
                if p.automotive:
                    meta.Study = 'Automotive'
                else:
                    meta.Study = 'Electrical Engineering'
                meta.Cohort = p.cohort
                meta.ECTS = p.ects
                meta.Studentnumber = p.idnumber
                meta.save()
                count += 1
            return render(
                request, 'base.html', {
                    'Message':
                    mark_safe('User meta updated for {} users. <br />'.format(
                        count) + print_list(write_errors)),
                    'return':
                    'osirisdata:list'
                })
    else:
        form = ConfirmForm()

    return render(
        request, 'osirisdata/osiris_to_meta_form.html', {
            'form': form,
            'formtitle': 'Confirm write to usermeta',
            'buttontext': 'Confirm'
        })
コード例 #13
0
 def clean_Group(self):
     group = self.cleaned_data['Group']
     if self.request.user.groups.count() == 1 and get_grouptype(
             '4') in self.request.user.groups.all():
         # user is groupadmin and not assistant/responsible.
         rw_groups = get_writable_admingroups(self.request.user)
         if group not in rw_groups:
             raise ValidationError(
                 "You are not allowed to create a project for that group. You are only allowed to "
                 "create projects for {}".format(print_list(rw_groups)))
     return group
コード例 #14
0
 def clean(self):
     """
     Do not allow users to be in both read and write members.
     :return:
     """
     dups = set(self.cleaned_data.get('readmembers')) & set(
         self.cleaned_data.get('writemembers'))
     if dups:
         raise ValidationError(
             "User(s) {} cannot be both read and write members. Please remove them from one of the fields."
             .format(print_list(list(dups))))
コード例 #15
0
def clean_attachment_default(self):
    """
    Check whether an attachment is valid

    :param self:
    :return:
    """
    file = clean_file_default(self)
    # this check is done both here and in the model
    if get_ext(file.name) not in settings.ALLOWED_PROJECT_ATTACHMENTS:
        raise ValidationError(
            'This file type is not allowed. Allowed types: ' +
            print_list(settings.ALLOWED_PROJECT_ATTACHMENTS))
    return file
コード例 #16
0
def clean_studentfile_default(self):
    """
    Clean function for studentfile form. Checks if the extension is in the allowed extensions for this type file.

    :param self:
    :return:
    """
    try:
        ftype = get_object_or_404(FileType, pk=self.data['Type'])
    except:
        raise ValidationError(
            'Please select a file type from the drop-down list.')
    file = clean_file_default(self)
    if get_ext(file.name) not in ftype.get_allowed_extensions():
        raise ValidationError(
            'This file extension is not allowed. Allowed extensions: ' +
            print_list(ftype.get_allowed_extensions()))
    return file
コード例 #17
0
def clean_email_default(email, allowed_domains):
    """
    Clean email address and check if it has allowed domain

    :param email: emailadress in string
    :param allowed_domains: FQDN of allowed domain of mail
    :return: cleaned lowercase email addresss string or ValidationError
    """
    email = email.strip('\r').strip().lower()
    if not mailPattern.match(email):
        raise forms.ValidationError(
            "Invalid email address ({}): Every line should contain one valid email address"
            .format(email))
    # check if the domain is allowed (for instance @tue.nl )
    domain = email.split('@')[1]
    if domain not in allowed_domains:
        raise forms.ValidationError(
            "This email domain is not allowed. Allowed domains: {}".format(
                print_list(allowed_domains)))
    return email
コード例 #18
0
def clean_image_default(self):
    """
    Check whether an uploaded image is valid and has right dimensions

    :param self:
    :return:
    """
    picture = clean_file_default(self)

    # this check is done both here and in the model, needed to prevent wrong files entering get_image_dimensions()
    if get_ext(picture.name) not in settings.ALLOWED_PROJECT_IMAGES:
        raise ValidationError(
            'This file type is not allowed. Allowed types: ' +
            print_list(settings.ALLOWED_PROJECT_IMAGES))

    w, h = get_image_dimensions(picture)
    if w < minw or h < minh:
        raise ValidationError(
            "The image is too small, it has to be at least " + str(minw) +
            "px by " + str(minh) + "px and is only " + str(w) + "px by " +
            str(h) + "px.")

    return picture
コード例 #19
0
def print_list_tag(list):
    return print_list(list)
コード例 #20
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,
    })