# ---- IMPORTS ---- # Imports for handing HTTP requests and responses from django.shortcuts import render_to_response from django.template import RequestContext from django.http import Http404, HttpResponseRedirect from django.core.urlresolvers import reverse # Import for handling Users from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User # Models and model handling from django.shortcuts import get_object_or_404 from models import Section, Folder, Lab # Forms from django import forms from widgets import SelectDateTimeWidget # Custom widget for selecting date and time for this site. # It doesn't work properly. # ---- FORMS FOR SECTION LISTING ---- class UserField(forms.ModelChoiceField): """ Extension of the ModelChoiceField. ModelChoiceField allows creates a choice field (default widget is a select element) and fills it with data from a provided queryset. This adds on custom labeling so as to show users in a better format. Ex: "(cjp5235): Provias, Colton J." """ def label_from_instance(self, obj): return "(%s): %s, %s" % (obj.username, obj.last_name, obj.first_name) class AddUserForm(forms.Form): """ Form for adding a new user to the database. """ psu_id = forms.RegexField(label='PSU ID', regex=r'^[a-zA-Z]+[0-9]+$', max_length=30) first_name = forms.CharField() last_name = forms.CharField() password = forms.CharField(widget=forms.PasswordInput) confirm_password = forms.CharField(widget=forms.PasswordInput) def clean_psu_id(self): """ Check to make sure that the PSU ID is not already in the database """ try: # This will throw a User.DoesNotExist exception if it can't find the PSU ID in the DB. user = User.objects.get(username__iexact=self.cleaned_data['psu_id']) except User.DoesNotExist: return self.cleaned_data['psu_id'] raise forms.ValidationError('This PSU ID is already in the system.') def clean(self): """ Check to make sure the passwords match. Note: clean is not field specific, so it can handle multiple fields simultaneously much more easily. Plus, the errors are returned as non-field-errors, which of course allow for the error to be displayed separately from the fields (ex: above the form). """ # Make sure the password fields aren't blank before checking them: if 'password' in self.cleaned_data and 'confirm_password' in self.cleaned_data: if self.cleaned_data['password'] != self.cleaned_data['confirm_password']: raise forms.ValidationError('Passwords do not match.') return self.cleaned_data def save(self): """ The form has been proven valid (clean) at this point. All of the data within it is now saved to a new user account. """ # Create the user using the create_user(username, email, password) method of the UserManager user = User.objects.create_user(self.cleaned_data['psu_id'], self.cleaned_data['psu_id'] + '@psu.edu', self.cleaned_data['password']) # Set the first name, last name, save the user, and return the user model for further editing # if needed user.first_name = self.cleaned_data['first_name'] user.last_name = self.cleaned_data['last_name'] user.save() return user # ---- VIEWS FOR SECTION LISTING ---- @login_required def sections(request): """ Displays a list of sections in the system. TODO: Restrict display for non-staff accounts so as to only display courses currently in. """ sections = Section.objects.all() return render_to_response('sections/main.html', {'sections': sections}, context_instance=RequestContext(request)) @login_required def section_edit(request): """ Returns the editor for each section (requested via AJAX request). """ # If the section exists, fetch it. Otherwise, throw an Http404 exception which will stop the # AJAX request. section = get_object_or_404(Section, pk=request.GET['s']) return render_to_response('sections/section_edit.html', {'section': section}, context_instance=RequestContext(request)) @login_required def section_display(request): """ Returns the normal display of each section. Should be displayed after closing the editor for each section. """ section = get_object_or_404(Section, pk=request.GET['s']) return render_to_response('sections/section_listing.html', {'section': section}, context_instance=RequestContext(request)) @login_required def section_instructors_add(request): """ Lets an instructor be added to a section. """ section = get_object_or_404(Section, pk=request.GET['s']) # Fetch a list of users that aren't involved in the section as an instructor or student. users = User.objects.exclude(id__in=section.instructors.values_list('id', flat=True)).exclude( id__in=section.students.values_list('id', flat=True)) if request.POST: # A request has been submitted to add a user as an instructor try: user = users.get(pk=int(request.POST['user'])) # Will throw a DoesNotExist exception if # the user is not found. section.instructors.add(user) # Add the user to the course # A very sneaky trick. Send a bit of javascript code that will trigger the editor to be # reloaded to show the new instructor. return render_to_response('sections/section_refresh.html', {'section_id': section.id}, context_instance=RequestContext(request)) except User.DoesNotExist: pass # Do nothing! # Display the form for adding an instructor to the course return render_to_response('sections/section_instructor_add.html', { 'users': users, 'section_id': section.id }, context_instance=RequestContext(request)) @login_required def section_instructors_add_new(request): """ Allows for a new user to be added as an instructor to a section. """ section = get_object_or_404(Section, pk=request.GET['s']) if request.POST: # Check to see if anything was submitted # Something was submitted. Fill the received values into the AddUserForm! form = AddUserForm(request.POST) if form.is_valid(): # Is the form clean/valid? # The form is clean, so run the save function and get the User object returned user = form.save() # Add user as an instructor to the course and refresh the editor section.instructors.add(user) section.save() return render_to_response('sections/section_refresh.html', {'section_id': section.id}, context_instance=RequestContext(request)) else: # No form submitted, so display a blank form. form = AddUserForm() # Display the form. Errors will be displayed if the form was submitted but did not pass the # is_valid test return render_to_response('sections/section_instructor_add_new.html', {'form': form, 'section_id': section.id}, context_instance=RequestContext(request)) @login_required def section_instructors_remove(request): """ Removes an instructor from a section """ section = get_object_or_404(Section, pk=request.GET['s']) user = get_object_or_404(User, pk=request.GET['u']) section.instructors.remove(user) # Display the updated editor return render_to_response('sections/section_edit.html', {'section': section}, context_instance=RequestContext(request)) @login_required def section_students_add(request): """ Allow for a student to be added to a course. Just like the section_instructors_add above. """ section = get_object_or_404(Section, pk=request.GET['s']) users = User.objects.exclude(id__in=section.instructors.values_list('id', flat=True)).exclude( id__in=section.students.values_list('id', flat=True)) if request.POST: try: user = users.get(pk=int(request.POST['user'])) section.students.add(user) return render_to_response('sections/section_refresh.html', {'section_id': section.id}, context_instance=RequestContext(request)) except User.DoesNotExist: pass return render_to_response('sections/section_student_add.html', {'users': users, 'section_id': section.id}, context_instance=RequestContext(request)) @login_required def section_students_add_new(request): """ Allows for a brand new student to be added to the course. Acts just like section_instructors_add_new above. POSSIBLE TODO: Make a generic view out of these to better abide by the DRY principle. """ section = get_object_or_404(Section, pk=request.GET['s']) if request.POST: form = AddUserForm(request.POST) if form.is_valid(): user = form.save() section.students.add(user) # Add the student to the course section.save() return render_to_response('sections/section_refresh.html', {'section_id': section.id}, context_instance=RequestContext(request)) else: form = AddUserForm() return render_to_response('sections/section_student_add_new.html', {'form': form, 'section_id': section.id}, context_instance=RequestContext(request)) @login_required def section_students_remove(request): """ Allows for a student to be removed...once again like a similar function above. """ section = get_object_or_404(Section, pk=request.GET['s']) user = get_object_or_404(User, pk=request.GET['u']) section.students.remove(user) return render_to_response('sections/section_edit.html', {'section': section}, context_instance=RequestContext(request)) # ---- FORMS FOR FOLDER VIEWS ---- class AddFolderForm(forms.Form): """ A very simple form for adding a folder to the section. """ name = forms.CharField() def save(self, section): """ Add a folder, save it, and return it, all in one line! """ return Folder(name=self.cleaned_data['name'], section=section).save() class AddLabForm(forms.Form): """ A more complex, yet still simple form for adding labs to folders. """ # Form fields. CharFields are TextInputs unless otherwise stated. name = forms.CharField() description = forms.CharField(widget=forms.Textarea) start_time = forms.DateTimeField(widget=SelectDateTimeWidget) # Painful widget! It barely works! end_time = forms.DateTimeField(widget=SelectDateTimeWidget) has_introduction = forms.BooleanField(required=False) # Apparently a boolean field can has_discussion = forms.BooleanField(required=False) # throw an exception if false. has_input_files = forms.BooleanField(required=False) # Thus why it isn't required. has_output_files = forms.BooleanField(required=False) max_team_size = forms.IntegerField(initial=1) # Default to solo work. def save(self, folder): """ Create a new Lab object, save it, and return it. Simple. Also, use the folder passed as an argument as the folder for the Lab object. """ return Lab( name = self.cleaned_data['name'], folder = folder, description = self.cleaned_data['description'], start_time = self.cleaned_data['start_time'], end_time = self.cleaned_data['end_time'], has_introduction = self.cleaned_data['has_introduction'], has_discussion = self.cleaned_data['has_discussion'], has_input_files = self.cleaned_data['has_input_files'], has_output_files = self.cleaned_data['has_output_files'], max_team_size = self.cleaned_data['max_team_size'] ).save() # ---- VIEWS FOR FOLDERS ---- @login_required def folder(request, section_id, folder_id=0): """ An extremely confusing function that needs to be cleaned up. It works, though! """ section = get_object_or_404(Section, pk=section_id) try: # Check to see if the instructor exists. If so, create the AddFolderForm section.instructors.get(pk=request.user.id) if request.POST: form = AddFolderForm(request.POST) if 'description' not in request.POST: # The form submitted is indeed the AddFolderForm if form.is_valid(): # rather than the AddLabForm form.save(section) form = AddFolderForm() # Display a fresh form! else: form = AddFolderForm() except User.DoesNotExist: # The user is not an instructor, so set form to form = None # None if folder_id == 0: # We are in the base of each section. folders = section.folder_set.all() return render_to_response('folders/folder_list.html', {'folders': folders, 'section': section, 'form': form}, context_instance=RequestContext(request)) # At this point, assume that we are within a folder itself try: # Get the folder and make sure it's a part of the section, all in one quick query statement! folder = section.folder_set.get(pk=folder_id) except Folder.DoesNotExist: raise Http404 # Final processing of the AddLabForm if form is not None: # If form is None, then the user was proven not to be an instructor # earlier. Don't bother with the form. if request.POST: # Lab form was submitted. Validate it, save it, and provide a new one! form = AddLabForm(request.POST) if form.is_valid(): form.save(folder) form = AddLabForm() else: form = AddLabForm() # Display the list of labs return render_to_response('folders/lab_list.html', {'folder': folder, 'section': section, 'form': form}, context_instance=RequestContext(request)) @login_required def folder_remove(request, section_id, folder_id): """ Display a confirmation for removing a folder. If there is POST data, continue with the removal. """ section = get_object_or_404(Section, pk=section_id) try: # Get the folder and make sure that the user is an instructor folder = section.folder_set.get(pk=folder_id) section.instructors.get(pk=request.user.id) except (Folder.DoesNotExist, User.DoesNotExist): # Something's fishy. Hide behind a 404! raise Http404 if request.POST: # The confirmation form was submitted. Just delete the folder! folder.delete() # Go to the main page of the section. return HttpResponseRedirect(reverse('folder-base', args=(section.id,))) return render_to_response('folders/delete_confirm.html', {'folder': folder, 'section': section}, context_instance=RequestContext(request)) # ---- LAB VIEWS ---- @login_required def lab_list(request, lab_id): """ Display a list of LabData objects. Most of this is handled in the templates. Nothing to see here! """ lab = get_object_or_404(Lab, pk=lab_id) return render_to_response('labs/list.html', {'lab': lab}, context_instance=RequestContext(request)) """ TODO: -LabData Detail -Parser Integration -ASCI File Uploading -Binary File Uploading -Archiving for downloading -Access Restrictions -PDF Generation """