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', })
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, })
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]
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))
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
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, })
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))
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
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, })
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()))
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' })
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' })
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
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))))
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
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
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
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
def print_list_tag(list): return print_list(list)
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, })