From db9da266b94991f8dc9a2a275adac7ba60b7b89c Mon Sep 17 00:00:00 2001 From: Yiwei Chen Date: Thu, 19 Sep 2019 11:24:28 -0400 Subject: [PATCH 01/44] Change placeholder width --- compsocsite/static/css/shared.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compsocsite/static/css/shared.css b/compsocsite/static/css/shared.css index 930503f7..698e45b7 100755 --- a/compsocsite/static/css/shared.css +++ b/compsocsite/static/css/shared.css @@ -422,7 +422,7 @@ th { white-space: nowrap; background-color: rgb(193, 214, 231); height: 40px; - width: 400px; + width: 550px; padding: 0; margin: 10px 0px; border-radius: 0.3rem; From cf0341c4f6024c4d92d85d1e328ca6ae46621c6d Mon Sep 17 00:00:00 2001 From: Yiwei Chen Date: Fri, 20 Sep 2019 19:03:12 -0400 Subject: [PATCH 02/44] Add animation to the voting UI --- compsocsite/static/js/voting.js | 140 ++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 61 deletions(-) diff --git a/compsocsite/static/js/voting.js b/compsocsite/static/js/voting.js index 37cd6687..6e8a9687 100644 --- a/compsocsite/static/js/voting.js +++ b/compsocsite/static/js/voting.js @@ -15,7 +15,7 @@ var commentTime = ""; var method = 1; // 1 is twoCol, 2 is oneCol, 3 is Slider var methodIndicator = "two_column"; var init_star = false; - +var animOffset = 200; // animation speed of ranking UIs, 200 = 0.2s var top_tier_layer = 0; function select(item){ @@ -397,52 +397,59 @@ function yesNoZeroSort( order ){ }); } -function changeCSS(){ - if(method == 1){ - $(".choice1").css("width", "550px"); - $(".empty"). css("width", "550px"); - $(".col-placeHolder").css("width", "550px"); - $("#left-sortable").children(".choice1").each(function(){ - size = $(this).children(":not(.ui-selected, .transporter)").size(); - //$(this).css("height", ((size-1)*40).toString() + "px"); - if(size > 4){ - size -= 1; - num = Math.ceil(size/3); - $(this).css("height", (num*40).toString() + "px"); - $(this).children(".tier").css( "height", (num*40).toString() + "px"); - $(this).children(".tier").css( "line-height", (num*40).toString() + "px"); - } - else{ - $(this).css("height", "40px"); - $(this).children(".tier").css( "height", "40px"); - $(this).children(".tier").css( "line-height", "40px"); +// change the behavior of the UI when the user change voting method +// or drop a new item +function changeCSS(){ + // if method is twocol + if(method == 1){ + $(".choice1").css("width", "550px"); + $(".empty"). css("width", "550px"); + $(".col-placeHolder").css("width", "550px"); - } - }); - } - else if(method == 2){ - $(".choice1").css("width", "800px"); - $(".empty"). css("width", "800px"); - $(".col-placeHolder").css("width", "800px"); - } - $("#one-sortable").children(".choice1").each(function(){ - size = $(this).children(":not(.ui-selected, .transporter)").size(); - if(size > 7){ - size -= 1; - num = Math.ceil(size/6); - $(this).css("height", (num*40).toString() + "px"); - $(this).children(".tier").css( "height", (num*40).toString() + "px"); - $(this).children(".tier").css( "line-height", (num*40).toString() + "px"); + // extend the height of choice1 box if there exists more than 3 list-element in a row + // vice versa + $("#left-sortable").children(".choice1").each(function(){ + size = $(this).children(":not(.ui-selected, .transporter)").size(); + //$(this).css("height", ((size-1)*40).toString() + "px"); + if(size > 4){ + size -= 1; + num = Math.ceil(size/3); + $(this).css("height", (num*40).toString() + "px"); + $(this).children(".tier").css( "height", (num*40).toString() + "px"); + $(this).children(".tier").css( "line-height", (num*40).toString() + "px"); + } + else{ + $(this).css("height", "40px"); + $(this).children(".tier").css( "height", "40px"); + $(this).children(".tier").css( "line-height", "40px"); + } + }); } - else{ - $(this).css("height", "40px"); - $(this).children(".tier").css( "height", "40px"); - $(this).children(".tier").css( "line-height", "40px"); - + // if method is onecol, extend the selection bar + else if(method == 2){ + $(".choice1").css("width", "800px"); + $(".empty"). css("width", "800px"); + $(".col-placeHolder").css("width", "800px"); } - }); + // extend the height of choice1 box if there exists more than 6 list-element in a row + // vice versa + $("#one-sortable").children(".choice1").each(function(){ + size = $(this).children(":not(.ui-selected, .transporter)").size(); + if(size > 7){ + size -= 1; + num = Math.ceil(size/6); + $(this).css("height", (num*40).toString() + "px"); + $(this).children(".tier").css( "height", (num*40).toString() + "px"); + $(this).children(".tier").css( "line-height", (num*40).toString() + "px"); + } + else{ + $(this).css("height", "40px"); + $(this).children(".tier").css( "height", "40px"); + $(this).children(".tier").css( "line-height", "40px"); + } + }); } @@ -854,7 +861,7 @@ $( document ).ready(function() { checkSubmission(); setupSortable(); }, 1000); - changeCSS(); + changeCSS(); function checkSubmission(){ if($("#left-sortable").children().size() > 0){ @@ -863,7 +870,6 @@ $( document ).ready(function() { else{ $(".submitbutton").prop("disabled", true); } - } // reinitalize the sortable function function setupSortable(){ @@ -886,12 +892,19 @@ $( document ).ready(function() { if (method == 1) { $(".col-placeHolder").css("width", "550px"); } else if (method == 2) { $(".col-placeHolder").css("width", "800px"); } }, + revert:'invalid', + start: function(e, ui){ + $(".col-placeHolder").hide(animOffset); // add animation for placeholder + }, + change: function (e,ui){ + $(".col-placeHolder").hide().show(animOffset);// add animation for placeholder + }, stop: function(e, ui) { - checkAll(); - removeSelected(); - resetEmpty(); - changeCSS(); - } + checkAll(); + removeSelected(); + resetEmpty(); + changeCSS(); + } }); $('.choice1').sortable({ @@ -903,7 +916,9 @@ $( document ).ready(function() { items: "li:not(.tier)", placeholder: "li-placeHolder", connectWith: str, - + start: function(e, ui){ + //$(".li-placeHolder").hide(animOffset); // add animation for placeholder + }, helper: function(e, item) { if (!item.hasClass('ui-selected')) { $('.ul').find('.ui-selected').removeClass('ui-selected'); @@ -919,18 +934,21 @@ $( document ).ready(function() { changeCSS(); }, change: function(e, ui) { - if (ui.placeholder.parent().hasClass("sortable-ties")) { - if (method == 1) { - $(".li-placeHolder").css("width", "550px"); - } - else if (method == 2) { - $(".li-placeHolder").css("width", "800px"); + // wait the animation to competer then change teh css of placeholder + $(".li-placeHolder").hide().show(animOffset, function(){ + if (ui.placeholder.parent().hasClass("sortable-ties")) { + if (method == 1) { + $(".li-placeHolder").css("width", "550px"); + } + else if (method == 2) { + $(".li-placeHolder").css("width", "800px"); + } + $(".li-placeHolder").css({ "float": "none", "height": "40px", "margin": "0px 0px" }); } - $(".li-placeHolder").css({ "float": "none", "height": "40px", "margin": "0px 0px" }); - } - else { - $(".li-placeHolder").css({ "float": "left", "width": "125px", "height": "35px", "margin": "2.5px 8px" }); - } + else { + $(".li-placeHolder").css({ "float": "left", "width": "125px", "height": "35px", "margin": "2.5px 8px" }); + } + }); changeCSS(); }, From a386297e8511ac650e8757a1faaae1d15e4efb0d Mon Sep 17 00:00:00 2001 From: Yiwei Chen Date: Fri, 20 Sep 2019 22:54:18 -0400 Subject: [PATCH 03/44] Fixed a bug that the placeholder don't have enough width in method onecol --- compsocsite/static/js/voting.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/compsocsite/static/js/voting.js b/compsocsite/static/js/voting.js index 6e8a9687..ebc18526 100644 --- a/compsocsite/static/js/voting.js +++ b/compsocsite/static/js/voting.js @@ -888,16 +888,21 @@ $( document ).ready(function() { placeholder: "col-placeHolder", handle: ".tier", //items: "ul", - change: function(e, ui) { - if (method == 1) { $(".col-placeHolder").css("width", "550px"); } - else if (method == 2) { $(".col-placeHolder").css("width", "800px"); } - }, revert:'invalid', start: function(e, ui){ - $(".col-placeHolder").hide(animOffset); // add animation for placeholder + $(".col-placeHolder").hide(); }, change: function (e,ui){ - $(".col-placeHolder").hide().show(animOffset);// add animation for placeholder + $(".col-placeHolder").hide().show(animOffset, function(){ + if (method == 1) { + $(".col-placeHolder").css("width", "550px"); + } + else if (method == 2) { + $(".col-placeHolder").css("width", "800px"); + } + $(".li-placeHolder").css({ "float": "none", "height": "40px", "margin": "0px 0px" }); + }); + changeCSS(); }, stop: function(e, ui) { checkAll(); From 41b95acb29149d779420748b12ae96120dd66191 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 20 Oct 2019 20:19:43 -0400 Subject: [PATCH 04/44] Create a new app mentors --- compsocsite/mentors/__init__.py | 0 compsocsite/mentors/admin.py | 3 + compsocsite/mentors/apps.py | 5 + compsocsite/mentors/forms.py | 9 + .../mentors/migrations/0001_initial.py | 20 +++ .../migrations/0002_auto_20191019_1631.py | 45 +++++ .../migrations/0003_auto_20191019_1920.py | 18 ++ .../migrations/0004_mentor_recommender.py | 18 ++ .../migrations/0005_auto_20191019_2040.py | 18 ++ .../mentors/migrations/0006_mentor_step.py | 18 ++ compsocsite/mentors/migrations/__init__.py | 0 compsocsite/mentors/models.py | 39 ++++ .../mentors/templates/mentors/apply.html | 55 ++++++ .../templates/mentors/apply_compensation.html | 42 +++++ .../mentors/apply_personal_info.html | 60 +++++++ .../templates/mentors/apply_preference.html | 59 ++++++ .../mentors/templates/mentors/index.html | 65 +++++++ .../templates/mentors/view_application.html | 73 ++++++++ compsocsite/mentors/tests.py | 3 + compsocsite/mentors/urls.py | 26 +++ compsocsite/mentors/views.py | 168 ++++++++++++++++++ 21 files changed, 744 insertions(+) create mode 100644 compsocsite/mentors/__init__.py create mode 100644 compsocsite/mentors/admin.py create mode 100644 compsocsite/mentors/apps.py create mode 100644 compsocsite/mentors/forms.py create mode 100644 compsocsite/mentors/migrations/0001_initial.py create mode 100644 compsocsite/mentors/migrations/0002_auto_20191019_1631.py create mode 100644 compsocsite/mentors/migrations/0003_auto_20191019_1920.py create mode 100644 compsocsite/mentors/migrations/0004_mentor_recommender.py create mode 100644 compsocsite/mentors/migrations/0005_auto_20191019_2040.py create mode 100644 compsocsite/mentors/migrations/0006_mentor_step.py create mode 100644 compsocsite/mentors/migrations/__init__.py create mode 100644 compsocsite/mentors/models.py create mode 100755 compsocsite/mentors/templates/mentors/apply.html create mode 100755 compsocsite/mentors/templates/mentors/apply_compensation.html create mode 100755 compsocsite/mentors/templates/mentors/apply_personal_info.html create mode 100755 compsocsite/mentors/templates/mentors/apply_preference.html create mode 100755 compsocsite/mentors/templates/mentors/index.html create mode 100755 compsocsite/mentors/templates/mentors/view_application.html create mode 100644 compsocsite/mentors/tests.py create mode 100644 compsocsite/mentors/urls.py create mode 100644 compsocsite/mentors/views.py diff --git a/compsocsite/mentors/__init__.py b/compsocsite/mentors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/compsocsite/mentors/admin.py b/compsocsite/mentors/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/compsocsite/mentors/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/compsocsite/mentors/apps.py b/compsocsite/mentors/apps.py new file mode 100644 index 00000000..5649da65 --- /dev/null +++ b/compsocsite/mentors/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MentorsConfig(AppConfig): + name = 'mentors' diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py new file mode 100644 index 00000000..346f71f6 --- /dev/null +++ b/compsocsite/mentors/forms.py @@ -0,0 +1,9 @@ +from django import forms + +# not using right now +class ApplyForm(forms.Form): + rin = forms.CharField(label='rin', max_length=100) + first_name = forms.CharField(label='fname', max_length=100) + last_name = forms.CharField(label='lname', max_length=100) + phone = forms.CharField(label='phone', max_length=100) + gpa = forms.CharField(label='gpa', max_length=100) diff --git a/compsocsite/mentors/migrations/0001_initial.py b/compsocsite/mentors/migrations/0001_initial.py new file mode 100644 index 00000000..4fa2fac2 --- /dev/null +++ b/compsocsite/mentors/migrations/0001_initial.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.1 on 2019-10-16 04:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Question', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + ] diff --git a/compsocsite/mentors/migrations/0002_auto_20191019_1631.py b/compsocsite/mentors/migrations/0002_auto_20191019_1631.py new file mode 100644 index 00000000..e7d9f3e8 --- /dev/null +++ b/compsocsite/mentors/migrations/0002_auto_20191019_1631.py @@ -0,0 +1,45 @@ +# Generated by Django 2.2.1 on 2019-10-19 21:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Course', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('class_title', models.CharField(max_length=4)), + ], + ), + migrations.CreateModel( + name='Mentor', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('applied', models.BooleanField(default=False)), + ('RIN', models.CharField(max_length=9)), + ('first_name', models.CharField(max_length=50)), + ('last_name', models.CharField(max_length=50)), + ('GPA', models.IntegerField()), + ('RPI_email', models.CharField(max_length=50)), + ('phone_number', models.CharField(max_length=50)), + ], + ), + migrations.CreateModel( + name='Professor', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_name', models.CharField(max_length=50)), + ('last_name', models.CharField(max_length=50)), + ('department', models.CharField(max_length=50)), + ], + ), + migrations.DeleteModel( + name='Question', + ), + ] diff --git a/compsocsite/mentors/migrations/0003_auto_20191019_1920.py b/compsocsite/mentors/migrations/0003_auto_20191019_1920.py new file mode 100644 index 00000000..7d7d6ace --- /dev/null +++ b/compsocsite/mentors/migrations/0003_auto_20191019_1920.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-10-20 00:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0002_auto_20191019_1631'), + ] + + operations = [ + migrations.RenameField( + model_name='mentor', + old_name='phone_number', + new_name='phone', + ), + ] diff --git a/compsocsite/mentors/migrations/0004_mentor_recommender.py b/compsocsite/mentors/migrations/0004_mentor_recommender.py new file mode 100644 index 00000000..3773e055 --- /dev/null +++ b/compsocsite/mentors/migrations/0004_mentor_recommender.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-10-20 01:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0003_auto_20191019_1920'), + ] + + operations = [ + migrations.AddField( + model_name='mentor', + name='recommender', + field=models.CharField(default='', max_length=50), + ), + ] diff --git a/compsocsite/mentors/migrations/0005_auto_20191019_2040.py b/compsocsite/mentors/migrations/0005_auto_20191019_2040.py new file mode 100644 index 00000000..18a764f5 --- /dev/null +++ b/compsocsite/mentors/migrations/0005_auto_20191019_2040.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-10-20 01:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0004_mentor_recommender'), + ] + + operations = [ + migrations.AlterField( + model_name='mentor', + name='recommender', + field=models.CharField(max_length=50), + ), + ] diff --git a/compsocsite/mentors/migrations/0006_mentor_step.py b/compsocsite/mentors/migrations/0006_mentor_step.py new file mode 100644 index 00000000..e6c29e89 --- /dev/null +++ b/compsocsite/mentors/migrations/0006_mentor_step.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-10-20 20:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0005_auto_20191019_2040'), + ] + + operations = [ + migrations.AddField( + model_name='mentor', + name='step', + field=models.IntegerField(default=1), + ), + ] diff --git a/compsocsite/mentors/migrations/__init__.py b/compsocsite/mentors/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py new file mode 100644 index 00000000..ed7998d5 --- /dev/null +++ b/compsocsite/mentors/models.py @@ -0,0 +1,39 @@ +from __future__ import unicode_literals + +from django.db import models +from django.utils.encoding import python_2_unicode_compatible +from django.contrib.auth.models import User + + +@python_2_unicode_compatible +class Mentor(models.Model): + + # already applied for this semester + applied = models.BooleanField(default = False) + + # name data of applicants + RIN = models.CharField(max_length=9) + first_name = models.CharField(max_length=50) + last_name = models.CharField(max_length=50) + + GPA = models.IntegerField() + RPI_email = models.CharField(max_length=50) + phone = models.CharField(max_length=50) # ??? + recommender = models.CharField(max_length=50) + + step = models.IntegerField(default = 1) + #owner = models.ForeignKey(User, on_delete=models.CASCADE) + #open = models.IntegerField(default=0) + def __str__(self): + return "" + +class Course(models.Model): + class_title = models.CharField(max_length=4) + class_number = models.IntegerField + + +class Professor(models.Model): + first_name = models.CharField(max_length=50) + last_name = models.CharField(max_length=50) + department = models.CharField(max_length=50) + diff --git a/compsocsite/mentors/templates/mentors/apply.html b/compsocsite/mentors/templates/mentors/apply.html new file mode 100755 index 00000000..04507cd8 --- /dev/null +++ b/compsocsite/mentors/templates/mentors/apply.html @@ -0,0 +1,55 @@ +{% extends 'polls/base.html' %} + +{% block content %} +{% if user.is_authenticated %} +
+

+ {% if messages %} +

    + {% for message in messages %} +
    + {{ message }} +
    + {% endfor %} +
+ {% endif %} +

+
+ {% if not applied %} + +
+
+
+ Steps +
+
+
    +
  • Agreement
  • +
  • Personal Information
  • +
  • Compensation and Responsibilities
  • +
+
+
+
+
+ +
+
+ Mentor application Form +
+
+ {% if step == 1 %} + {% include "mentors/apply_personal_info.html" %} + {% elif step == 2 %} + {% include "mentors/apply_compensation.html" %} + {% elif step == 3 %} + {% include "mentors/apply_preference.html" %} + {% endif %} +
+
+ {% else %} +
Session Expired
+ {% endif %} + +{% endif %} +{% endblock %} diff --git a/compsocsite/mentors/templates/mentors/apply_compensation.html b/compsocsite/mentors/templates/mentors/apply_compensation.html new file mode 100755 index 00000000..4f4ca82e --- /dev/null +++ b/compsocsite/mentors/templates/mentors/apply_compensation.html @@ -0,0 +1,42 @@ +{% extends 'mentors/apply.html' %} +{% load staticfiles %} + + + +{% block content %} +
+ {% csrf_token %} + Compensation and Responsibilities + +
We currently have funding for a fixed number of paid programming mentors. Therefore, if you apply for pay, you may be asked to work for credit instead. Course credit counts as a graded 1-credit or 2-credit free elective. In general, if you work an average of 1-3 hours per week, you will receive 1 credit; if you average 4 or more hours per week, you will receive 2 credits. Note that no more than 4 credits may be earned as an undergraduate (spanning all courses and all semesters). Please do not apply for credit if you have already earned 4 credits as a mentor; apply only for pay. +
+ +
+ +
+ + Pay ($14/hour)   + Course Credit   + No Preference +   « required +
+
+ + I have been employed and paid by RPI before +
+ +
+ + +
For a paid position, you must follow the given instructions to ensure you are paid; otherwise, another candidate will be selected. More specifically, you will be required to have a student employment card. This requires you to have a federal I-9 on file. Bring appropriate identification with you to Amos Eaton 109 if this is your first time working for RPI; click here for a list of acceptable documents. Note that original documents are required; copies cannot be accepted. +
+
Detailed instructions are listed here: http://finance.rpi.edu/update.do?catcenterkey=184
+
Please note that to be paid, you will be required to submit your specific hours in SIS every two weeks. This must be completed to get paid on time, and any late submissions will incur a fee charged to the department.
+
If you are unable to attend your assigned labs or office hours (e.g., illness, interview, conference, etc.), you must notify your graduate lab TA and instructor and help arrange a substitute mentor. Unexcused absences will cause you to either earn a lower letter grade or not be asked to mentor again.
+ +
+ +
+
+ +{% endblock %} \ No newline at end of file diff --git a/compsocsite/mentors/templates/mentors/apply_personal_info.html b/compsocsite/mentors/templates/mentors/apply_personal_info.html new file mode 100755 index 00000000..5431b219 --- /dev/null +++ b/compsocsite/mentors/templates/mentors/apply_personal_info.html @@ -0,0 +1,60 @@ +{% extends 'mentors/apply.html' %} +{% load staticfiles %} + + + +{% block content %} +
+ {% csrf_token %} + Personal Information +
+ + + (e.g. 660000007) +   « required +
+ +
+ + +   « required +
+ +
+ + +   « required +
+ +
+ + @rpi.edu +   « required +
+ +
+ + + (e.g. 518-597-xxxx) +   « required +
+ +
+ + + (e.g. 3.67) +   « required +
+ +
+ + +   « required +
+ +
+ +
+
+ +{% endblock %} \ No newline at end of file diff --git a/compsocsite/mentors/templates/mentors/apply_preference.html b/compsocsite/mentors/templates/mentors/apply_preference.html new file mode 100755 index 00000000..41ede3f5 --- /dev/null +++ b/compsocsite/mentors/templates/mentors/apply_preference.html @@ -0,0 +1,59 @@ +{% extends 'mentors/apply.html' %} +{% load staticfiles %} + + + +{% block content %} +
+ {% csrf_token %} + Course selection + + + + + + + +
+ +
Please select you prefrence of course below: (this part is unfinished, it's normal if you see nothing) +
+
    + {% if currentSelection %} + +
    + {% for s in currentSelection %} + {% if s %} +
      + +
      #{{ forloop.counter }}
      + + {% for selection in s %} +
    • + + {% if selection.image %} + + {% endif %} + + {% if selection.imageURL != Null %} + + {% endif %} + + {{ selection }} + + {% if selection.item_description %} + + {% endif %} +
    • + {% endfor %} + +
    + {% endif %} + {% endfor %} + + {% endif %} +
    +
+ + +{% endblock %} diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html new file mode 100755 index 00000000..a8679510 --- /dev/null +++ b/compsocsite/mentors/templates/mentors/index.html @@ -0,0 +1,65 @@ +{% extends 'polls/base.html' %} + +{% block content %} +{% if user.is_authenticated %} +
+

+ {% if messages %} +

    + {% for message in messages %} +
    + {{ message }} +
    + {% endfor %} +
+ {% endif %} +

+
+
+
+
+ Mentor Application +
+ +
+ + {% if applied %} + + + Mentor apllication success + + + + + + {% else %} + Begin to apply: + + {% endif %} +
View
Apply
+
+
+
+
+ Groups you can join +
+
+ + {% if opengroups %} + + {% for group in opengroups %} + {% if request.user != group.owner and request.user not in group.members.all %} + + + + + {% endif %} + {% endfor %} + + {% endif %} +
{{ group.name }}Join
+
+
+
+{% endif %} +{% endblock %} diff --git a/compsocsite/mentors/templates/mentors/view_application.html b/compsocsite/mentors/templates/mentors/view_application.html new file mode 100755 index 00000000..3e2ea0c7 --- /dev/null +++ b/compsocsite/mentors/templates/mentors/view_application.html @@ -0,0 +1,73 @@ +{% extends 'polls/base.html' %} + +{% block content %} +{% if user.is_authenticated %} +
+

+ {% if messages %} +

    + {% for message in messages %} +
    + {{ message }} +
    + {% endfor %} +
+ {% endif %} +

+
+
+
+
+ View Your Application +
+
+
+ {% for application in applications %} +
+ {{ application.RIN }} +
+
+ {{ application.first_name }} {{ application.last_name }} + +
+
+ {{ application.RPI_email }} +
+
+ {{ application.phone }} +
+
+ {{ application.GPA }} +
+
+ {{ application.recommender }} +
+ {% endfor %} + +
+
+
+
+
+ Groups you can join +
+
+ + {% if opengroups %} + + {% for group in opengroups %} + {% if request.user != group.owner and request.user not in group.members.all %} + + + + + {% endif %} + {% endfor %} + + {% endif %} +
{{ group.name }}Join
+
+
+
+{% endif %} +{% endblock %} diff --git a/compsocsite/mentors/tests.py b/compsocsite/mentors/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/compsocsite/mentors/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py new file mode 100644 index 00000000..20ce5e56 --- /dev/null +++ b/compsocsite/mentors/urls.py @@ -0,0 +1,26 @@ +from django.conf.urls import url +from django.contrib.auth.decorators import login_required +from django.views.decorators.cache import cache_page +from . import views +from django.urls import path + + + +app_name = 'mentors' +urlpatterns = [ + url(r'^$', login_required(views.IndexView.as_view()), name='index'), + url(r'^apply$', login_required(views.ApplyView.as_view()), name='apply'), + # personal info + url(r'^applyfunc1/$', views.applystep, name='applyfunc1'), + # compensation and responsbility + url(r'^applyfunc2/$', views.applystep, name='applyfunc2'), + # RANKING of preference of course of studnet + url(r'^applyfunc3/$', views.applystep, name='applyfunc3'), + + url(r'^view_application$', login_required(views.view_applyView.as_view()), name='view_application'), + url(r'^withdrawfunc/$', views.withdraw, name='withdrawfunc'), + url(r'^apply_personal_info$', login_required(views.ApplyPersonalInfoView.as_view()), name='apply_personal_info'), + url(r'^apply_compensation$', login_required(views.ApplyCompensationView.as_view()), name='apply_compensation'), + url(r'^apply_prefernece$', login_required(views.ApplyPreferenceView.as_view()), name='apply_prefernece'), + +] diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py new file mode 100644 index 00000000..72048824 --- /dev/null +++ b/compsocsite/mentors/views.py @@ -0,0 +1,168 @@ +from .models import * +from appauth.models import * +from groups.models import * +import datetime +import os +import time +import collections + +from django.shortcuts import render, get_object_or_404, redirect +from django.http import HttpResponseRedirect, HttpResponse, HttpRequest +from django.urls import reverse +from django import views +from django.db.models import Q + +from django.utils import timezone +from django.template import RequestContext +from django.shortcuts import render_to_response +from django.contrib import messages +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.decorators import login_required +from django.core import mail +from prefpy.mechanism import * +from prefpy.allocation_mechanism import * +from prefpy.gmm_mixpl import * +from prefpy.egmm_mixpl import * +from django.conf import settings +from multipolls.models import * + +from django.http import HttpResponseRedirect +from django.shortcuts import render + +from .forms import ApplyForm + +import json +import threading +import itertools +import numpy as np +import random +import csv +import logging + +class IndexView(views.generic.ListView): + """ + Define homepage view, inheriting ListView class, which specifies a context variable. + + Note that login is required to view the items on the page. + """ + + template_name = 'mentors/index.html' + def get_context_data(self, **kwargs): + ctx = super(IndexView, self).get_context_data(**kwargs) + + # check if there exist a mentor application + ctx['applied'] = Mentor.applied + return ctx + + def get_queryset(self): + """Override function in parent class and return all questions.""" + + return Mentor.objects.all() + +class ApplyView(views.generic.ListView): + template_name = 'mentors/apply.html' + def get_context_data(self, **kwargs): + ctx = super(ApplyView, self).get_context_data(**kwargs) + # check if there exist a mentor application + ctx['applied'] = Mentor.applied + ctx['applications'] = Mentor.objects.all() + + if (Mentor.applied): + ctx['step'] = Mentor.step + else: + ctx['step'] = 1 + Mentor.step = 1 + + return ctx + def get_queryset(self): + return Mentor.objects.all() + +class view_applyView(views.generic.ListView): + template_name = 'mentors/view_application.html' + def get_context_data(self, **kwargs): + ctx = super(view_applyView, self).get_context_data(**kwargs) + # check if there exist a mentor application + ctx['applied'] = Mentor.applied + ctx['applications'] = Mentor.objects.all() + + return ctx + def get_queryset(self): + return Mentor.objects.all() + +# view of personal infomation page +class ApplyPersonalInfoView(views.generic.ListView): + template_name = 'mentors/apply_personal_info.html' + def get_context_data(self, **kwargs): + ctx = super(ApplyPersonalInfoView, self).get_context_data(**kwargs) + # check if there exist a mentor application + ctx['applied'] = Mentor.applied + ctx['applications'] = Mentor.objects.all() + + return ctx + def get_queryset(self): + return Mentor.objects.all() + +# view of compensation and reponsibility page +class ApplyCompensationView(views.generic.ListView): + template_name = 'mentors/apply_compensation.html' + def get_context_data(self, **kwargs): + ctx = super(ApplyCompensationView, self).get_context_data(**kwargs) + # check if there exist a mentor application + ctx['applied'] = Mentor.applied + ctx['applications'] = Mentor.objects.all() + ctx['step'] = Mentor.step + + return ctx + + def get_queryset(self): + return Mentor.objects.all() + +# view of preference of a student applicant page +class ApplyPreferenceView(views.generic.ListView): + template_name = 'mentors/apply_preference.html' + def get_context_data(self, **kwargs): + ctx = super(ApplyPreferenceView, self).get_context_data(**kwargs) + # check if there exist a mentor application + ctx['applied'] = Mentor.applied + ctx['applications'] = Mentor.objects.all() + ctx['step'] = Mentor.step + + # === need more for course data === + + return ctx + + def get_queryset(self): + return Mentor.objects.all() + + +def applystart(request): + new_applica = Mentor() + +# apply step +def applystep(request): + if request.method == 'POST': + # initate a new mentor applicant + if ( Mentor.step == 1): + new_applicant = Mentor() + new_applicant.RIN = request.POST['rin'] + new_applicant.first_name = request.POST['fname'] + new_applicant.last_name = request.POST['lname'] + new_applicant.GPA = request.POST['gpa'] + new_applicant.phone = request.POST['phone'] + new_applicant.RPI_email = request.POST['email'] + new_applicant.recommender = request.POST['recommender'] + + new_applicant.save() + + #Mentor.applied = True + Mentor.step += 1 + + return render(request, 'mentors/apply.html', {'step': Mentor.step}) + +# withdraw application, should add semester later +def withdraw(request): + if request.method == 'GET': + + Mentor.objects.all().delete() + Mentor.applied = False + return HttpResponseRedirect(reverse('mentors:index')) \ No newline at end of file From 6563745a965c3e964c0906a2e4847df09fb03530 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 20 Oct 2019 20:20:41 -0400 Subject: [PATCH 05/44] Add a new css file for form submission --- compsocsite/static/css/submission_form.css | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100755 compsocsite/static/css/submission_form.css diff --git a/compsocsite/static/css/submission_form.css b/compsocsite/static/css/submission_form.css new file mode 100755 index 00000000..aa34ed24 --- /dev/null +++ b/compsocsite/static/css/submission_form.css @@ -0,0 +1,63 @@ +label { + color: rgb(63, 63, 63); + font-weight: bold; + display: block; + width: 150px; + float: left; +} + +.inputline{ + height: 30px; + padding: 20px 15px; +} +.textline{ + padding: 10px 15px; +} + +.vspacewithline{ + padding: 10px 15px; + +} +* {box-sizing: border-box} + +/* Style the tab */ +.tab { + float: left; + border: 1px solid #ccc; + background-color: #f1f1f1; + width: 30%; + height: 300px; +} + +/* Style the buttons that are used to open the tab content */ +.tab button { + display: block; + background-color: inherit; + color: black; + padding: 22px 16px; + width: 100%; + border: none; + outline: none; + text-align: left; + cursor: pointer; + transition: 0.3s; +} + +/* Change background color of buttons on hover */ +.tab button:hover { + background-color: #ddd; +} + +/* Create an active/current "tab button" class */ +.tab button.active { + background-color: #ccc; +} + +/* Style the tab content */ +.tabcontent { + float: left; + padding: 0px 12px; + border: 1px solid #ccc; + width: 70%; + border-left: none; +} \ No newline at end of file From 242c5fc483dfb75b1599bab496f47015c97bb7a7 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 20 Oct 2019 20:25:11 -0400 Subject: [PATCH 06/44] Add a new column for mentor application --- compsocsite/polls/templates/polls/base.html | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compsocsite/polls/templates/polls/base.html b/compsocsite/polls/templates/polls/base.html index 365c4fda..33b7ba96 100755 --- a/compsocsite/polls/templates/polls/base.html +++ b/compsocsite/polls/templates/polls/base.html @@ -20,9 +20,10 @@ - - - + + + + @@ -169,7 +170,9 @@
  • Multi-Polls
  • Classes
  • Groups
  • -
  • Sessions
  • +
  • Sessions
  • +
  • TA&Mentor Apply
  • + {% if not request.flavour == "mobile" %}
  • From 21d73693599bcca0b29c7a9f27dcd38b9ecacf9c Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 9 Nov 2019 22:04:08 -0500 Subject: [PATCH 07/44] Add matching simulation --- compsocsite/mentors/CS_Course.csv | 14 + compsocsite/mentors/forms.py | 256 ++++++++- compsocsite/mentors/match.py | 339 ++++++++++++ .../mentors/migrations/0001_initial.py | 50 +- .../migrations/0002_auto_20191019_1631.py | 45 -- .../migrations/0002_remove_mentor_step.py | 17 + .../migrations/0003_auto_20191019_1920.py | 18 - .../migrations/0004_mentor_recommender.py | 18 - .../migrations/0005_auto_20191019_2040.py | 18 - .../mentors/migrations/0006_mentor_step.py | 18 - compsocsite/mentors/models.py | 78 ++- .../mentors/templates/mentors/apply.html | 28 +- .../mentors/apply_personal_info.html | 55 +- .../templates/mentors/apply_preference.html | 490 ++++++++++++++++-- .../mentors/templates/mentors/index.html | 90 ++-- compsocsite/mentors/urls.py | 14 +- compsocsite/mentors/views.py | 180 ++++++- 17 files changed, 1398 insertions(+), 330 deletions(-) create mode 100644 compsocsite/mentors/CS_Course.csv create mode 100755 compsocsite/mentors/match.py delete mode 100644 compsocsite/mentors/migrations/0002_auto_20191019_1631.py create mode 100644 compsocsite/mentors/migrations/0002_remove_mentor_step.py delete mode 100644 compsocsite/mentors/migrations/0003_auto_20191019_1920.py delete mode 100644 compsocsite/mentors/migrations/0004_mentor_recommender.py delete mode 100644 compsocsite/mentors/migrations/0005_auto_20191019_2040.py delete mode 100644 compsocsite/mentors/migrations/0006_mentor_step.py diff --git a/compsocsite/mentors/CS_Course.csv b/compsocsite/mentors/CS_Course.csv new file mode 100644 index 00000000..f7c23fb0 --- /dev/null +++ b/compsocsite/mentors/CS_Course.csv @@ -0,0 +1,14 @@ +Intro to Computer Programming,CSCI,1010,P_1 +Computer Science I,CSCI,1100,P_2 +Beginning Prog for Engineers,CSCI,1190,P_3 +Data Structures,CSCI,1200,P_4 +Foundations of Computer Science,CSCI,2200,P_5 +Intro to Algorithms,CSCI,2300,P_6 +Computer Organization,CSCI,2500,P_7 +Principles of Software,CSCI,2600,P_8 +Machine Learning from Data,CSCI,4100,P_3 +Network Programming,CSCI,4220,P_10 +Crypto and Network Security I,CSCI,4230,P_11 +Database Systems,CSCI,4380,P_2 +Programming Languages,CSCI,4430,P_5 +Large-Scale Programming and Testing,CSCI,4460,P_4 \ No newline at end of file diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 346f71f6..8752dfbc 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -1,9 +1,251 @@ from django import forms +from .models import * +from django.forms import ModelForm +from django.utils.translation import gettext_lazy as _ -# not using right now -class ApplyForm(forms.Form): - rin = forms.CharField(label='rin', max_length=100) - first_name = forms.CharField(label='fname', max_length=100) - last_name = forms.CharField(label='lname', max_length=100) - phone = forms.CharField(label='phone', max_length=100) - gpa = forms.CharField(label='gpa', max_length=100) +# import crispy form plugin +from crispy_forms.helper import FormHelper +from crispy_forms.layout import * +from crispy_forms.bootstrap import * + +class MentorApplicationfoForm(ModelForm): + class Meta: + model = Mentor + fields = '__all__' + ''' + help_texts = { + 'RIN': _(' *Required'), + 'first_name': _(' *Required'), + 'last_name': _(' *Required'), + 'GPA': _(' *Required'), + 'email': _(' *Required'), + 'phone': _(' *Required'), + 'recommender': _(' *Required'), + } + ''' + widgets = { + 'RIN': forms.TextInput(attrs={'placeholder': ' 661680100'}), + 'GPA': forms.TextInput(attrs={'placeholder': ' 3.6'}), + 'email': forms.TextInput(attrs={'placeholder': ' xxx@rpi.email'}), + 'phone': forms.TextInput(attrs={'placeholder': ' 5185941234'}), + } + + def __init__(self, *args, **kwargs): + super(MentorApplicationfoForm, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.form_id = 'id-exampleForm' + self.helper.form_class = 'blueForms' + self.helper.form_method = 'post' + #self.helper.form_action = 'applyfunc1' + courses = Course.objects.all() + course_layout = Div() + + # Add courses fields + for course in courses: + course_layout.append(HTML("
    ")) + course_layout.append( HTML("
    " + course.subject +" "+ course.number +" "+ course.name + "
    ") ) + grades = ( + ('a', 'A'), + ('a-', 'A-'), + ('b+', 'B+'), + ('b', 'B'), + ('b-', 'B-'), + ('c+', 'C+'), + ('c', 'C'), + ('c-', 'C-'), + ('d+', 'D+'), + ('d', 'D'), + ('f', 'F'), + ('p', 'Progressing'), + ('n', 'Not Taken'), + ) + choices_YN = ( + ('Y', 'YES'), + ('N', 'NO'), + ) + self.fields[course.name+ "_grade"] = forms.ChoiceField( + choices = grades, + label = "Grade on this course:", + ) + self.initial[course.name+ "_grade"] = 'n' + + self.fields[course.name+ "_exp"] = forms.ChoiceField(choices = choices_YN, label = 'Have you mentored this class before? ') + self.initial[course.name+ "_exp"] = 'N' + + course_layout.append(Field(course.name+ "_grade", label_class = "long_label")) + #course_layout.append(HTML('

    ')) + + course_layout.append(InlineRadios(course.name+ "_exp")) + + course_layout.append(HTML("
    ")) + + # Time slot choices + SCHEDULING = ( + ('M1', 'YES'), + ('M2', 'NO'), + ) + self.helper.layout = Layout( + Accordion( + AccordionGroup('== PERSONAL INFORMATION ==', + HTML("
    "), + Field('RIN', 'first_name', 'last_name', 'email', 'phone', 'GPA', css_class = ""), + HTML(""" +
    Please provide the name of someone in the CS Department who can recommend you. You are encouraged, but not required, to contact this person. +
    """), + HTML(""" +
    Please provide only a name here (describe additional circumstances in the freeform textbox below).
    + """), + Field('recommender', css_class = "inputline"), + HTML("
    "), + ), + AccordionGroup('== COMPENSATION AND RESPONSIBILITIES ==', + HTML("
    "), + HTML(""" +
    + We currently have funding for a fixed number of paid programming mentors. Therefore, if you apply for pay, you may be asked to work for credit instead. Course credit counts as a graded 1-credit or 2-credit free elective. In general, if you work an average of 1-3 hours per week, you will receive 1 credit; if you average 4 or more hours per week, you will receive 2 credits. Note that no more than 4 credits may be earned as an undergraduate (spanning all courses and all semesters). Please do not apply for credit if you have already earned 4 credits as a mentor; apply only for pay. +
    + """), + Field('compensation', css_class = ""), + HTML(""" +
    + For a paid position, you must follow + the given instructions to ensure you are paid; otherwise,  + another candidate will be selected.  More specifically, you  + will be required to have a student employment card. + This requires you to have a federal I-9 on file.  Bring appropriate  + identification with you to Amos Eaton 109 if this is your first  + time working for RPI; +
    + """), + HTML("
    "), + + ), + + AccordionGroup('== COURSE SELECTIONS ==', + HTML("
    "), + course_layout, + HTML("
    "), + ), + + AccordionGroup('== COURSE RANKINGS ==', + HTML("
    "), + + HTML('''Please rank the courses which you prefer to mentor, #1 means the highest prioity, and #2 means the second priority...etc. This will help us to allocate your position.'''), + + HTML('''
      +
      + {% for course in courses %} + {% if course %} +
        + +
        #{{ forloop.counter }}
        +
      • + {{ course.subject }} {{course.number}} +
      • +
      + {% endif %} + {% endfor %} +
    + '''), + HTML("
    "), + + ), + AccordionGroup('== SCHEDULING ==', + HTML("
    "), + HTML("Time slot plugin here"), + HTML("
    "), + ), + ) + ) + self.helper.form_method = 'POST' + self.helper.label_class = "my_label" + self.helper.add_input(Submit('submit', 'Submit', onclick="VoteUtil.submitPref();")) + + # Overide the save func + def save(self, *args, **kwargs): + new_applicant = Mentor() + new_applicant.RIN = self.cleaned_data["RIN"] + new_applicant.first_name = self.cleaned_data["first_name"] + new_applicant.last_name = self.cleaned_data["last_name"] + new_applicant.GPA = self.cleaned_data["GPA"] + new_applicant.phone = self.cleaned_data["phone"] + new_applicant.compensation = self.cleaned_data["compensation"] + new_applicant.save() + print(new_applicant) + + # Save Grades on the course average + for course in Course.objects.all(): + course_grade = course.name+ "_grade" + course_exp = course.name+ "_exp" + + new_grade = Grade() + new_grade.student = new_applicant + new_grade.course = course + new_grade.student_grade = self.cleaned_data[course_grade] # Grade on this course + if (self.cleaned_data[course_exp] == 'Y'): # Mentor Experience + new_grade.mentor_exp = True + else: + new_grade.mentor_exp = False + + # if the student has failed the class, or is progressing, we consider he did not take the course + if (new_grade.student_grade != 'p' and new_grade.student_grade != 'n' and new_grade.student_grade != 'f'): + new_grade.have_taken = False + else: + new_grade.have_taken = True + new_grade.save() + print(new_grade.course.name + ": " + new_grade.student_grade.upper()) + + +class CompensationForm(ModelForm): + class Meta: + model = Mentor + fields = ('compensation',) + + + +''' +class GradeForm(forms.ModelForm): + class Meta: + model = Grade + fields = ('student_grade', 'mentor_exp') + help_texts= { + 'student_grade': _("Have you taken this course and earned a grade? If so, please specify the grade: "), + 'mentor_exp': _("Have you mentored this class before? "), + } + +class GradeForm2(forms.Form): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + courses = Course.objects.all() + for i in range(len(courses) + 1): + field_name = 'courses_%s' % (i,) + grades = ( + ('a', 'A'), + ('a-', 'A-'), + ('b+', 'B+'), + ('b', 'B'), + ('b-', 'B-'), + ('c+', 'C+'), + ('c', 'C'), + ('c-', 'C-'), + ('d+', 'D+'), + ('d', 'D'), + ('f', 'F'), + ('p', 'Progressing'), + ('n', 'Not Taken'), + ) + + self.fields[field_name] = forms.ChoiceField(choices = grades) + self.initial[field_name] = 'n' + help_texts = { + field_name: _("Have you taken this course and earned a grade? If so, please specify the grade: "), + } + try: + #self.initial[field_name] = 'n' + True + except IndexError: + self.initial[field_name] = '' + # create an extra blank field + # field_name = 'interest_%s' % (i + 1,) + # self.fields[field_name] = forms.CharField(required=False) + ''' \ No newline at end of file diff --git a/compsocsite/mentors/match.py b/compsocsite/mentors/match.py new file mode 100755 index 00000000..b8cb6aad --- /dev/null +++ b/compsocsite/mentors/match.py @@ -0,0 +1,339 @@ +from collections import defaultdict, deque + +class Matcher: + + #constructs a Matcher instance + + #studentPrefs is a dict from student to class ranking as a list + #e.g. "student1" -> ["class1", "class3", "class2"] + #the ranking doesn't need to be complete + #all missing classes are treated as "worse than nothing" + + #studentFeatures is a dict from student to dict(class -> feature vector) + #e.g "student1" -> {"class1" -> (0, 1, 3.8), "class2" -> (1, 2, 3.8)} + #that is, studentFeatures[s][c] is student s's feature vector in terms of class c + #each student needs to have a feature vector for each class in their ranking + + #classCaps is a dict from class to maximum capacity + #e.g. "class1" -> 5 + + #classFeatures is a dict from class to feature vector + #e.g. "class1" -> (98, 70, 65) + + #the dot product of a student's feature vector with a particuar class's feature vector is its score + #in terms of that class + def __init__(self, studentPrefs, studentFeatures, classCaps, classFeatures): + + self.studentPrefs = studentPrefs + self.classCaps = classCaps + + + #a student may not have a feature vector for every class + #in that case, we convert all the inner dict(class -> vector) objects to defaultdicts + #that way, if we make a call studentFeatures[s][c], but student s doesn't have a feature vector for class c, + #instead of getting a ValueError, we get an empty tuple () + #doing a dot product with an empty tuple will give a score of 0, which is what we want + studentFeatures = {s: defaultdict(tuple, d) for s, d in studentFeatures.items()} + + #dot product of a and b + #only does dot over the first min(len(a), len(b)) elements + def dot(a, b): + #it's ok if lengths aren't equal, will just take shortest length, + #so we don't really need this assert + #assert len(a) == len(b) + return sum(x*y for x, y in zip(a, b)) + + #returns a complete ranking over students for one specific class c + def makeClassPref(c): + + #return list of students, sorted by dot product, then student name (for tiebreaking) + #if a student doesn't have a feature vector for this class, we will get (), which will make their score 0 + return sorted(studentPrefs.keys(), key=lambda s: (dot(classFeatures[c], studentFeatures[s][c]), s), reverse=True) + + #call makeClassPref on each class to make the preferences + self.classPrefs = {c: makeClassPref(c) for c in classFeatures.keys()} + + + #output variables + self.studentMatching = {} #student -> class + self.classMatching = defaultdict(list) #class -> [students] + + #we index preferences at initialization to avoid expensive lookups when matching + self.classRank = defaultdict(dict) #classRank[c][s] is c's ranking of s + self.studentRank = defaultdict(dict) #studentRank[s][c] is s's ranking of c + + #if ranking isn't present, treated as less than none + + for c, prefs in self.classPrefs.items(): + for i, s in enumerate(prefs): + self.classRank[c][s] = i + + for s, prefs in studentPrefs.items(): + for i, c in enumerate(prefs): + self.studentRank[s][c] = i + + + + #Test whether s prefers c over c2. + def prefers(self, s, c, c2): + ranking = self.studentRank[s] + + if c in ranking: + + if c2 in ranking: + #both in ranking + + #return normally + return ranking[c] < ranking[c2] + + else: + #c in ranking, but c2 not in ranking + + #c is preferred + return True + + else: + if c2 in ranking: + #c not in ranking, but c2 in ranking + + #c2 is preferred + return False + + else: + #both not in ranking + + #none, none: false + #strr, none: false + #none, strr: true + #strr, strr: c < c2 + + #None represents no one. it can be thought of as being after the last ranked element, + #and before the unranked ones + + if c2 == None: + #either c is str and c2 is none, or both none + #either way, c is not preferred over c2 + return False + + if c == None: + #c is none and c2 is str + #so c, being no one, is preferred over unranked c2 + return True + + + #if we get here, none isn't involved + #both c and c2 are unranked str's + + #preference based on alphabetical order + return c < c2 + + + #Return the student favored by c after s. + def after(self, c, s): + + #TODO extra checking here might not be needed as classes have full ranking + + if s not in self.classRank[c]: + #TODO will this happen? probably not + print(f"student {s} is not in {c}'s ranking") + #assert False + + #in case it does happen, + #we return the next alphabetical student who is also not ranked + #it is expensive though + + students = sorted(self.studentPrefs.keys()) + + #if we're trying to find the next student after "no one", + #we search from the beginning + if s == None: + i = 0 + + else: + i = students.index(s)+1 + + while i < len(students) and students[i] in self.classRank[c]: + i += 1 + + + if i >= len(students): + #this alg in't perfect. we're mixing what None means + #none usually means "no one" + #but here it means there isn't a next student + #this probably doesn't matter much, as this code will probably not be used + return None + + print(f"using {students[i]}") + return students[i] + + #index of student following s in list of prefs + i = self.classRank[c][s] + 1 + + if i >= len(self.classPrefs[c]): + #no other students are prefered. + return None + + return self.classPrefs[c][i] + + + + #Try to match all classes with their next preferred spouse. + #does class-proposing Gale-Shapely + def match(self): + + #simple combination of a queue with a set + #the set is used for quick "contains" lookups + class SmartQueue: + + def __init__(self): + self.queue = deque() + self.set = set() + + def put(self, x): + self.queue.append(x) + self.set.add(x) + + def pop(self): + x = self.queue.popleft() + self.set.remove(x) + return x + + def __contains__(self, x): + return x in self.set + + def __len__(self): + return len(self.queue) + + + #classes (full ranking) + #students (partial ranking) + + #queue of classes we still have to match + queue = SmartQueue() + + #next is a map from class to next student to propose to + #starts as first preferences + next = {} + + #mapping from students to current class + studentMatching = {} + + #the current capacity for each class + currCap = defaultdict(int) + + #initalize + #we do this in a loop so we only iterate over self.classPrefs once + for c, rank in self.classPrefs.items(): + #we start with all the classes in the queue + queue.put(c) + + #and all the classes will propose to their top ranking student + next[c] = rank[0] + + + #while we still have classes to match + while len(queue) > 0: + + #take class off list + c = queue.pop() + + #make proposals for the remaining capacity + for i in range(self.classCaps[c] - currCap[c]): + + #next student for c to propose to + s = next[c] + + #if we run out of students to propose to + if s == None: + #we break, finished matching, but having less than max capacity + break + + #student after s in c's list of prefs + #"next-next" student for c to propose to + next[c] = self.after(c, s) + + + #if s is already matched + if s in studentMatching: + + #current class that s is in + c2 = studentMatching[s] + + assert c2 != c + + #if s prefers c more than current class + if self.prefers(s, c, c2): + + #old class c2 becomes available again + + #unmatch to old class + currCap[c2] -= 1 + + #if c2 isn't already scheduled to match, put it in the queue + if c2 not in queue: + queue.put(c2) + + #s becomes matched to c + studentMatching[s] = c + currCap[c] += 1 + + + #otherwise, we're rejected + #just go to the next proposal + + #else, s is unmatched, so c gets them + else: + #s becomes matched to c + studentMatching[s] = c + currCap[c] += 1 + + + #we finished the proposals for this class for this round + #now, does this class need another round? + + #if we aren't full, and haven't been "none'd", we re-add ourself + #"none'd" meaning we ran out of students to propose to + if currCap[c] < self.classCaps[c] and next[c] != None: + queue.put(c) + + + #now we've matched all classes, so we're done + + #populate studentMatching + self.studentMatching = studentMatching + + #populate classMatching from studentMatching + for s, c in studentMatching.items(): + self.classMatching[c].append(s) + + + return self.classMatching + + #check if the mapping of studentMatching to husbands is stable + #TODO this doesn't look at unmatched students or classes. is that a problem? + def isStable(self, studentMatching=None, verbose=False): + + if studentMatching is None: + studentMatching = self.studentMatching + + for s, c in studentMatching.items(): + + i = self.classRank[c][s] + + preferred = self.classPrefs[c][:i] + + for p in preferred: + + #it's possible p is unmatched + #in that case, c2 is None + c2 = None + if p in studentMatching: + c2 = studentMatching[p] + + #check if p prefers us over current matching + #if c2 is none, this just checks if p prefers us to nobody + if self.prefers(p, c, c2): + if verbose: + print(f"{c}'s marriage to {s} is unstable:\n{c} prefers {p} over {s} and {p} prefers {c} over her current husband {c2}") + return False + return True diff --git a/compsocsite/mentors/migrations/0001_initial.py b/compsocsite/mentors/migrations/0001_initial.py index 4fa2fac2..5ae132cd 100644 --- a/compsocsite/mentors/migrations/0001_initial.py +++ b/compsocsite/mentors/migrations/0001_initial.py @@ -1,6 +1,9 @@ -# Generated by Django 2.2.1 on 2019-10-16 04:42 +# Generated by Django 2.2.1 on 2019-11-02 17:13 +import django.core.validators from django.db import migrations, models +import django.db.models.deletion +import mentors.models class Migration(migrations.Migration): @@ -12,9 +15,52 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Question', + name='Course', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('subject', models.CharField(max_length=4)), + ('number', models.CharField(default='1000', max_length=4)), + ('name', models.CharField(default='none', max_length=50)), + ('instructor', models.CharField(default='none', max_length=50)), + ('feature_cumlative_GPA', models.IntegerField(default=0)), + ('feature_has_taken', models.IntegerField(default=0)), + ('feature_course_GPA', models.IntegerField(default=0)), + ('feature_mentor_exp', models.IntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='Mentor', + fields=[ + ('applied', models.BooleanField(default=False)), + ('step', models.IntegerField(default=1)), + ('RIN', models.CharField(max_length=9, primary_key=True, serialize=False, validators=[django.core.validators.MinLengthValidator(9)])), + ('first_name', models.CharField(max_length=50)), + ('last_name', models.CharField(max_length=50)), + ('GPA', mentors.models.MinMaxFloat()), + ('email', models.CharField(max_length=50)), + ('phone', models.CharField(max_length=50)), + ('recommender', models.CharField(max_length=50)), + ('compensation', models.CharField(choices=[('1', 'Pay'), ('2', 'Credit'), ('3', 'No Preference')], default='1', max_length=1)), + ], + ), + migrations.CreateModel( + name='Professor', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_name', models.CharField(max_length=50)), + ('last_name', models.CharField(max_length=50)), + ('department', models.CharField(max_length=50)), + ], + ), + migrations.CreateModel( + name='Grade', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('student_grade', models.CharField(choices=[('a', 'A'), ('a-', 'A-'), ('b+', 'B+'), ('b', 'B'), ('b-', 'B-'), ('c+', 'C+'), ('c', 'C'), ('c-', 'C-'), ('d+', 'D+'), ('d', 'D'), ('f', 'F'), ('p', 'Progressing'), ('n', 'Not Taken')], default='n', max_length=1)), + ('have_taken', models.BooleanField(default=False)), + ('mentor_exp', models.BooleanField(default=False)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Mentor')), ], ), ] diff --git a/compsocsite/mentors/migrations/0002_auto_20191019_1631.py b/compsocsite/mentors/migrations/0002_auto_20191019_1631.py deleted file mode 100644 index e7d9f3e8..00000000 --- a/compsocsite/mentors/migrations/0002_auto_20191019_1631.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 2.2.1 on 2019-10-19 21:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Course', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('class_title', models.CharField(max_length=4)), - ], - ), - migrations.CreateModel( - name='Mentor', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('applied', models.BooleanField(default=False)), - ('RIN', models.CharField(max_length=9)), - ('first_name', models.CharField(max_length=50)), - ('last_name', models.CharField(max_length=50)), - ('GPA', models.IntegerField()), - ('RPI_email', models.CharField(max_length=50)), - ('phone_number', models.CharField(max_length=50)), - ], - ), - migrations.CreateModel( - name='Professor', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_name', models.CharField(max_length=50)), - ('last_name', models.CharField(max_length=50)), - ('department', models.CharField(max_length=50)), - ], - ), - migrations.DeleteModel( - name='Question', - ), - ] diff --git a/compsocsite/mentors/migrations/0002_remove_mentor_step.py b/compsocsite/mentors/migrations/0002_remove_mentor_step.py new file mode 100644 index 00000000..95cc69b6 --- /dev/null +++ b/compsocsite/mentors/migrations/0002_remove_mentor_step.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.1 on 2019-11-09 20:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='mentor', + name='step', + ), + ] diff --git a/compsocsite/mentors/migrations/0003_auto_20191019_1920.py b/compsocsite/mentors/migrations/0003_auto_20191019_1920.py deleted file mode 100644 index 7d7d6ace..00000000 --- a/compsocsite/mentors/migrations/0003_auto_20191019_1920.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-10-20 00:20 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0002_auto_20191019_1631'), - ] - - operations = [ - migrations.RenameField( - model_name='mentor', - old_name='phone_number', - new_name='phone', - ), - ] diff --git a/compsocsite/mentors/migrations/0004_mentor_recommender.py b/compsocsite/mentors/migrations/0004_mentor_recommender.py deleted file mode 100644 index 3773e055..00000000 --- a/compsocsite/mentors/migrations/0004_mentor_recommender.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-10-20 01:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0003_auto_20191019_1920'), - ] - - operations = [ - migrations.AddField( - model_name='mentor', - name='recommender', - field=models.CharField(default='', max_length=50), - ), - ] diff --git a/compsocsite/mentors/migrations/0005_auto_20191019_2040.py b/compsocsite/mentors/migrations/0005_auto_20191019_2040.py deleted file mode 100644 index 18a764f5..00000000 --- a/compsocsite/mentors/migrations/0005_auto_20191019_2040.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-10-20 01:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0004_mentor_recommender'), - ] - - operations = [ - migrations.AlterField( - model_name='mentor', - name='recommender', - field=models.CharField(max_length=50), - ), - ] diff --git a/compsocsite/mentors/migrations/0006_mentor_step.py b/compsocsite/mentors/migrations/0006_mentor_step.py deleted file mode 100644 index e6c29e89..00000000 --- a/compsocsite/mentors/migrations/0006_mentor_step.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-10-20 20:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0005_auto_20191019_2040'), - ] - - operations = [ - migrations.AddField( - model_name='mentor', - name='step', - field=models.IntegerField(default=1), - ), - ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index ed7998d5..a67b3cb7 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -3,33 +3,89 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.contrib.auth.models import User +from django.core.validators import MinLengthValidator @python_2_unicode_compatible + +# a helper function to ensure min and max value +class MinMaxFloat(models.FloatField): + def __init__(self, min_value=None, max_value=None, *args, **kwargs): + self.min_value, self.max_value = min_value, max_value + super(MinMaxFloat, self).__init__(*args, **kwargs) + + def formfield(self, **kwargs): + defaults = {'min_value': self.min_value, 'max_value' : self.max_value} + defaults.update(kwargs) + return super(MinMaxFloat, self).formfield(**defaults) + +# the model for a mentor applicant class Mentor(models.Model): # already applied for this semester applied = models.BooleanField(default = False) + #step = models.IntegerField(default = 1) - # name data of applicants - RIN = models.CharField(max_length=9) - first_name = models.CharField(max_length=50) - last_name = models.CharField(max_length=50) + # Personal Info of applicants + RIN = models.CharField(max_length=9, validators=[MinLengthValidator(9)], primary_key=True) + first_name = models.CharField(max_length=50) # first name + last_name = models.CharField(max_length=50) # last name + + GPA = MinMaxFloat(min_value = 0.0, max_value = 4.0) - GPA = models.IntegerField() - RPI_email = models.CharField(max_length=50) + email = models.CharField(max_length=50) phone = models.CharField(max_length=50) # ??? recommender = models.CharField(max_length=50) - step = models.IntegerField(default = 1) - #owner = models.ForeignKey(User, on_delete=models.CASCADE) - #open = models.IntegerField(default=0) + # Compensation Choices + compensation_choice = ( + ('1', 'Pay'), + ('2', 'Credit'), + ('3', 'No Preference'), + ) + compensation = models.CharField(max_length=1, choices=compensation_choice, default='1') + + def __str__(self): return "" +# The model for a course class Course(models.Model): - class_title = models.CharField(max_length=4) - class_number = models.IntegerField + subject = models.CharField(max_length=4) # e.g CSCI + number = models.CharField(max_length=4, default="1000") # e.g 1100 + name = models.CharField(max_length=50, default = 'none') # e.g Intro to programming + instructor = models.CharField(max_length=50, default = 'none') # Instrcutor's name + + feature_cumlative_GPA = models.IntegerField(default=0) + feature_has_taken = models.IntegerField(default=0) + feature_course_GPA = models.IntegerField(default=0) + feature_mentor_exp = models.IntegerField(default=0) + + +class Grade(models.Model): + student = models.ForeignKey(Mentor, on_delete=models.CASCADE) + course = models.ForeignKey(Course, on_delete=models.CASCADE) + + grades = ( + ('a', 'A'), + ('a-', 'A-'), + ('b+', 'B+'), + ('b', 'B'), + ('b-', 'B-'), + ('c+', 'C+'), + ('c', 'C'), + ('c-', 'C-'), + ('d+', 'D+'), + ('d', 'D'), + ('f', 'F'), + ('p', 'Progressing'), + ('n', 'Not Taken'), + ) + + student_grade = models.CharField(max_length=1, choices = grades, default='n') # The student's grade of this course + have_taken = models.BooleanField(default = False) # Whether this studnet have taken this course + mentor_exp = models.BooleanField(default = False) # Whether this studnet have mentored this course + class Professor(models.Model): diff --git a/compsocsite/mentors/templates/mentors/apply.html b/compsocsite/mentors/templates/mentors/apply.html index 04507cd8..0a52153d 100755 --- a/compsocsite/mentors/templates/mentors/apply.html +++ b/compsocsite/mentors/templates/mentors/apply.html @@ -2,6 +2,8 @@ {% block content %} {% if user.is_authenticated %} + +

    {% if messages %} @@ -17,34 +19,18 @@

    {% if not applied %} -
    -
    -
    - Steps -
    -
    -
      -
    • Agreement
    • -
    • Personal Information
    • -
    • Compensation and Responsibilities
    • -
    -
    -
    -
    +
    Mentor application Form
    +
    - {% if step == 1 %} - {% include "mentors/apply_personal_info.html" %} - {% elif step == 2 %} - {% include "mentors/apply_compensation.html" %} - {% elif step == 3 %} - {% include "mentors/apply_preference.html" %} - {% endif %} + {% load crispy_forms_tags %} + {% crispy apply_form apply_form.helper %} + {{ form.errors }}
    {% else %} diff --git a/compsocsite/mentors/templates/mentors/apply_personal_info.html b/compsocsite/mentors/templates/mentors/apply_personal_info.html index 5431b219..2c8efb5d 100755 --- a/compsocsite/mentors/templates/mentors/apply_personal_info.html +++ b/compsocsite/mentors/templates/mentors/apply_personal_info.html @@ -5,56 +5,9 @@ {% block content %}
    - {% csrf_token %} - Personal Information -
    - - - (e.g. 660000007) -   « required -
    - -
    - - -   « required -
    - -
    - - -   « required -
    - -
    - - @rpi.edu -   « required -
    - -
    - - - (e.g. 518-597-xxxx) -   « required -
    - -
    - - - (e.g. 3.67) -   « required -
    - -
    - - -   « required -
    - -
    - -
    -
    + Personal Information + {% csrf_token %} + {{ apply_form.as_p }} + {% endblock %} \ No newline at end of file diff --git a/compsocsite/mentors/templates/mentors/apply_preference.html b/compsocsite/mentors/templates/mentors/apply_preference.html index 41ede3f5..1ac98c26 100755 --- a/compsocsite/mentors/templates/mentors/apply_preference.html +++ b/compsocsite/mentors/templates/mentors/apply_preference.html @@ -4,56 +4,444 @@ {% block content %} -
    - {% csrf_token %} - Course selection - - - - - - - -
    - -
    Please select you prefrence of course below: (this part is unfinished, it's normal if you see nothing) -
    -
      - {% if currentSelection %} - -
      - {% for s in currentSelection %} - {% if s %} -
        - -
        #{{ forloop.counter }}
        - - {% for selection in s %} -
      • - - {% if selection.image %} - - {% endif %} - - {% if selection.imageURL != Null %} - - {% endif %} - - {{ selection }} - - {% if selection.item_description %} - - {% endif %} -
      • - {% endfor %} - -
      - {% endif %} - {% endfor %} - - {% endif %} -
      -
    - +
    + {% csrf_token %} + + + +
    + Course Selections +
    + Below is a list of courses likely to need mentors. + Please check all courses for which you are able to mentor. + Note that + CSCI 1010 and CSCI 1100 are in Python, + CSCI 1190 is in MATLAB, + CSCI 1200 is in C++, + CSCI 2300 is in Python/C++, + CSCI 2500 is in C/Assembly, + and CSCI 2600 is in Java. + For each of the courses you have taken, please specify the letter grade you earned in the course at RPI (or select "AP" if you earned AP credit for CSCI 1100). +
    +
    + Note that you cannot be a mentor for a course you will be taking in the same semester or for a course you plan to take in the future. + You are however allowed to mentor for courses you have never taken; + be sure to describe equivalent courses or experiences in the general comments textbox below. +
    + +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    + +
    + +         + I am qualified to mentor this course +
    +         + I have taken this course at RPI before and earned grade: + +
    +         + I have mentored this RPI course before +
    +
    +
    + {% endblock %} +
      + {% if currentSelection %} + +
      + {% for course in courses %} + {% if course %} +
        + +
        #{{ forloop.counter }}
        + {% for selection in s %} +
      • + {{course.name}} +
      • + {% endfor %} + +
      \ No newline at end of file diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index a8679510..2eacfec7 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -15,51 +15,55 @@ {% endif %}

      -
      -
      -
      - Mentor Application -
      - -
      - - {% if applied %} - - - Mentor apllication success - - - - - - {% else %} - Begin to apply: - - {% endif %} -
      View
      Apply
      -
      +
      +
      +
      + Mentor Application
      -
      -
      - Groups you can join -
      -
      - - {% if opengroups %} - - {% for group in opengroups %} - {% if request.user != group.owner and request.user not in group.members.all %} - - - - - {% endif %} - {% endfor %} - - {% endif %} -
      {{ group.name }}Join
      -
      + +
      + + {% if applied %} + + + Mentor apllication success + + + + + + {% else %} + Begin to apply: + + {% endif %} +
      View
      Apply
      + +
      +
      +
      +
      +
      + {% csrf_token %} +

      Add Course (DEV)

      + +
      + +
      + {% csrf_token %} +

      Add Student Random

      + + +
      + +
      + {% csrf_token %} +

      Match

      + +
      +
      +
      +
      {% endif %} {% endblock %} diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index 20ce5e56..fd9e0623 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -11,16 +11,22 @@ url(r'^$', login_required(views.IndexView.as_view()), name='index'), url(r'^apply$', login_required(views.ApplyView.as_view()), name='apply'), # personal info - url(r'^applyfunc1/$', views.applystep, name='applyfunc1'), + url(r'^apply/$', views.applystep, name='applyfunc1'), # compensation and responsbility - url(r'^applyfunc2/$', views.applystep, name='applyfunc2'), + #url(r'^applyfunc2/$', views.applystep, name='applyfunc2'), # RANKING of preference of course of studnet - url(r'^applyfunc3/$', views.applystep, name='applyfunc3'), + url(r'^apply/$', views.applystep, name='applyfunc3'), + + url(r'^addcoursefunc/$', views.addcourse, name='addcoursefunc'), + url(r'^addStudentRandomfunc/$', views.addStudentRandom, name='addStudentRandomfunc'), + url(r'^matchfunc/$', views.StartMatch, name='matchfunc'), + + url(r'^searchcoursefunc/$', views.addcourse, name='searchcoursefunc'), url(r'^view_application$', login_required(views.view_applyView.as_view()), name='view_application'), url(r'^withdrawfunc/$', views.withdraw, name='withdrawfunc'), url(r'^apply_personal_info$', login_required(views.ApplyPersonalInfoView.as_view()), name='apply_personal_info'), url(r'^apply_compensation$', login_required(views.ApplyCompensationView.as_view()), name='apply_compensation'), - url(r'^apply_prefernece$', login_required(views.ApplyPreferenceView.as_view()), name='apply_prefernece'), + #url(r'^apply_prefernece/$', login_required(views.CourseAutocomplete.as_view()), name='apply_prefernece'), ] diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 72048824..abb8d6fc 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -28,8 +28,8 @@ from django.http import HttpResponseRedirect from django.shortcuts import render - -from .forms import ApplyForm +from dal import autocomplete +from .forms import * import json import threading @@ -38,6 +38,8 @@ import random import csv import logging +import random as r +from .match import Matcher class IndexView(views.generic.ListView): """ @@ -66,7 +68,8 @@ def get_context_data(self, **kwargs): # check if there exist a mentor application ctx['applied'] = Mentor.applied ctx['applications'] = Mentor.objects.all() - + ctx['step'] = Mentor.step + if (Mentor.applied): ctx['step'] = Mentor.step else: @@ -134,35 +137,166 @@ def get_context_data(self, **kwargs): def get_queryset(self): return Mentor.objects.all() - -def applystart(request): - new_applica = Mentor() - # apply step def applystep(request): if request.method == 'POST': # initate a new mentor applicant - if ( Mentor.step == 1): - new_applicant = Mentor() - new_applicant.RIN = request.POST['rin'] - new_applicant.first_name = request.POST['fname'] - new_applicant.last_name = request.POST['lname'] - new_applicant.GPA = request.POST['gpa'] - new_applicant.phone = request.POST['phone'] - new_applicant.RPI_email = request.POST['email'] - new_applicant.recommender = request.POST['recommender'] + form = MentorApplicationfoForm(request.POST) + if form.is_valid(): + form.save() + print("yes") + else: + print(form.errors) + + else: # display empty form + print("empty") + form = MentorApplicationfoForm() - new_applicant.save() - #Mentor.applied = True - Mentor.step += 1 - - return render(request, 'mentors/apply.html', {'step': Mentor.step}) + #Mentor.step += 1 + #return HttpResponseRedirect(reverse('groups:members', args=(group.id,))) + courses = Course.objects.all() + + return render(request, 'mentors/apply.html', {'courses': courses, 'apply_form': form}) + # withdraw application, should add semester later def withdraw(request): if request.method == 'GET': - Mentor.objects.all().delete() Mentor.applied = False - return HttpResponseRedirect(reverse('mentors:index')) \ No newline at end of file + return HttpResponseRedirect(reverse('mentors:index')) + + +def addcourse(request): + if request.method == 'POST': + Course.objects.all().delete() + #print(new_course.class_title + new_course.class_number + new_course.class_name + "successfully added") + with open("mentors/CS_Course.csv") as f: + reader = csv.reader(f) + for row in reader: + _ = Course.objects.get_or_create( + name = row[0], + subject = row[1], + number = row[2], + instructor = row[3], + ) + print(row[0] + " " + row[1] + " " + row[2] + " successfully added.") + + return render(request, 'mentors/index.html', {}) + +# Randomly add students +def addStudentRandom(request): + if request.method == 'POST': + Mentor.objects.all().delete() + + num_students = request.POST['num_students'] + for i in range(int(num_students)): + new_applicant = Mentor() + new_applicant.RIN = str(661680900 + i) + new_applicant.first_name = "student_" + new_applicant.last_name = str(i) + new_applicant.GPA = round(random.uniform(2.5, 4)*100)/100 # simple round + new_applicant.phone = 518596666 + new_applicant.save() + + for course in Course.objects.all(): + new_grade = Grade(id=None) + #glist = ['a','a-','b+','b','b-','c+','c','c-','d+','d','f','p','n'] + #new_grade.id = None + glist = ['a','a','a-','a','a-','b+','b','b-','n'] + + new_grade.student_grade = random.choice(glist) + if (new_grade.student_grade != 'p' and new_grade.student_grade != 'n' and new_grade.student_grade != 'f'): + new_grade.have_taken = True + new_grade.mentor_exp = random.choice([True, False]) + else: + new_grade.have_taken = False + new_grade.mentor_exp = False + + #new_grade.have_taken = random.choice([True, False]) + #new_grade.mentor_exp = random.choice([True, False]) + new_grade.course = course + new_grade.student = new_applicant + new_grade.save() + + #print("Add a new student: " + new_applicant.first_name + new_applicant.last_name + ": GPA: " + str(new_applicant.GPA)) + print("students now: " + str(len(Mentor.objects.all()))) + return render(request, 'mentors/index.html', {}) + + +def StartMatch(request): + if request.method == 'POST': + grade_weights = { 'a': 80, + 'a-': 30, + 'b+': 3.33, + 'b': 3, + 'b-': 2.67, + 'c+': 2.33, + 'c': 2, + 'c-': 1.67, + 'd+': 1.33, + 'd': 1, + 'f': 0, + 'p': 0, + 'n': 0} + + # begin matching: + studentFeatures = {} + for s in Mentor.objects.all(): + studentFeatures_per_course = {} + for c in Course.objects.all(): + item = Grade.objects.filter(student = s, course = c).first() + studentFeatures_per_course.update( + {c.name:( + s.GPA/4*100, + grade_weights.get(item.student_grade)/4*100, + int(item.have_taken)*100, + int(item.mentor_exp)*100 + ) + } + ) + + studentFeatures.update({s.RIN: studentFeatures_per_course}) + + + numFeatures = 4 # number of features we got + classes = [course.name for course in Course.objects.all()] + classCaps = {c: r.randint(3, 10) for c in classes} + students = [studnet.RIN for studnet in Mentor.objects.all()] + numClasses = len(Course.objects.all()) + + studentPrefs = {s: [c for c in r.sample(classes, r.randint(numClasses, numClasses ))] for s in students} + #print(studentPrefs) + #studentFeatures = {s: {c: tuple(r.randint(0, 10) for i in range(numFeatures)) for c in classRank} for s, classRank in studentPrefs.items()} + #print(studentFeatures) + #classFeatures = {c: (r.randint(3, 10), r.randint(3, 10), r.randint(1, 10), r.randint(1, 10)) for c in classes} + classFeatures = {c: (0, 1000, 5, 5) for c in classes} + + matcher = Matcher(studentPrefs, studentFeatures, classCaps, classFeatures) + classMatching = matcher.match() + + assert matcher.isStable() + print("matching is stable\n") + + #print out some classes and students + for i, (course, student_list) in enumerate(classMatching.items()): + print(course) + for s in student_list: + this_student = Mentor.objects.filter(RIN = s).first() + query = Grade.objects.filter(student = this_student, course = c) + item = query.first() + print(" "+s + " cumlative GPA: " + str(this_student.GPA).upper() + " grade: " + item.student_grade.upper() + ", has mentor exp: " + str(item.mentor_exp) ) + + print() + + unmatchedClasses = set(classes) - classMatching.keys() + unmatchedStudents = set(students) - matcher.studentMatching.keys() + + #unmatchedStudents uses the non-returned output matcher.studentMatching + #it's a dict from student to class they're in + + print(f"{len(unmatchedClasses)} classes with no students") + print(f"{len(unmatchedStudents)} students not in a class") + return render(request, 'mentors/index.html', {}) + From b72e8c20b96cb2094b6ca76daa19a046d312b0ab Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 10 Nov 2019 13:51:47 -0500 Subject: [PATCH 08/44] change css --- compsocsite/static/css/submission_form.css | 79 +++++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/compsocsite/static/css/submission_form.css b/compsocsite/static/css/submission_form.css index aa34ed24..89e3c601 100755 --- a/compsocsite/static/css/submission_form.css +++ b/compsocsite/static/css/submission_form.css @@ -1,22 +1,83 @@ -label { - color: rgb(63, 63, 63); - font-weight: bold; - display: block; - width: 150px; - float: left; +.accordion-heading{ + font-size: 66px; + color: red; + +} +.my_label { + color: rgb(63, 63, 63); + font-weight: bold; + display: block; + float: left; + width: 33%; + display: inline-block; +} +label.radio.inline{ + width: 33%; + padding: 0px 15px; +} +.course_title{ + font: 80px; + color: red; + font-weight: bold; +} +.control-group{ + width:100%; +} +.course_block{ + width:100%; + height:200px; +} + +.course_choice { + white-space: nowrap; + background:rgb(223, 222, 222); + height: 40px; + line-height: 40px; + width: 650px; + padding: 0; + margin: 6px 0px; + border: 0; + border-radius: 0.1rem; + flex-wrap: wrap; } +.course-element { + flex: 0 50%; + display: inline-block; + float: left; + margin: 2.5px 4px; + cursor: default; + width: 175px; + height: 35px; + line-height: 35px; + background-color: white; + text-align: center; + font-size: 12px; + color: #252525; + border: 1px solid rgb(145, 138, 138); + border-radius: 0.2rem; +} +.course-element.ui-selecting { + background-color: rgb(109, 108, 106) +} +.course-element.ui-selected { + background-color: rgb(176, 197, 214) +} .inputline{ - height: 30px; - padding: 20px 15px; + padding: 10px 15px; + width:100%; + } + .textline{ padding: 10px 15px; } .vspacewithline{ - padding: 10px 15px; + padding: 10px 15px; + width:100%; + height: 1px; } * {box-sizing: border-box} From f3ab2cc57fc7e4662167300f35b9ed07bd35e06f Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Mon, 11 Nov 2019 17:54:59 -0500 Subject: [PATCH 09/44] Fix bugs in the simualtion round --- compsocsite/mentors/views.py | 58 +++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index abb8d6fc..8d24c630 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -28,7 +28,6 @@ from django.http import HttpResponseRedirect from django.shortcuts import render -from dal import autocomplete from .forms import * import json @@ -44,10 +43,10 @@ class IndexView(views.generic.ListView): """ Define homepage view, inheriting ListView class, which specifies a context variable. - + Note that login is required to view the items on the page. """ - + template_name = 'mentors/index.html' def get_context_data(self, **kwargs): ctx = super(IndexView, self).get_context_data(**kwargs) @@ -58,7 +57,7 @@ def get_context_data(self, **kwargs): def get_queryset(self): """Override function in parent class and return all questions.""" - + return Mentor.objects.all() class ApplyView(views.generic.ListView): @@ -182,14 +181,14 @@ def addcourse(request): instructor = row[3], ) print(row[0] + " " + row[1] + " " + row[2] + " successfully added.") - + return render(request, 'mentors/index.html', {}) -# Randomly add students +# Randomly add students def addStudentRandom(request): if request.method == 'POST': Mentor.objects.all().delete() - + num_students = request.POST['num_students'] for i in range(int(num_students)): new_applicant = Mentor() @@ -199,13 +198,13 @@ def addStudentRandom(request): new_applicant.GPA = round(random.uniform(2.5, 4)*100)/100 # simple round new_applicant.phone = 518596666 new_applicant.save() - + for course in Course.objects.all(): new_grade = Grade(id=None) #glist = ['a','a-','b+','b','b-','c+','c','c-','d+','d','f','p','n'] #new_grade.id = None - glist = ['a','a','a-','a','a-','b+','b','b-','n'] - + glist = ['a','a-','b+','b','b-','c','c+''n'] + new_grade.student_grade = random.choice(glist) if (new_grade.student_grade != 'p' and new_grade.student_grade != 'n' and new_grade.student_grade != 'f'): new_grade.have_taken = True @@ -219,7 +218,7 @@ def addStudentRandom(request): new_grade.course = course new_grade.student = new_applicant new_grade.save() - + #print("Add a new student: " + new_applicant.first_name + new_applicant.last_name + ": GPA: " + str(new_applicant.GPA)) print("students now: " + str(len(Mentor.objects.all()))) return render(request, 'mentors/index.html', {}) @@ -227,8 +226,8 @@ def addStudentRandom(request): def StartMatch(request): if request.method == 'POST': - grade_weights = { 'a': 80, - 'a-': 30, + grade_weights = { 'a': 4, + 'a-': 3.69, 'b+': 3.33, 'b': 3, 'b-': 2.67, @@ -240,23 +239,23 @@ def StartMatch(request): 'f': 0, 'p': 0, 'n': 0} - + # begin matching: studentFeatures = {} for s in Mentor.objects.all(): studentFeatures_per_course = {} for c in Course.objects.all(): item = Grade.objects.filter(student = s, course = c).first() - studentFeatures_per_course.update( + studentFeatures_per_course.update( {c.name:( - s.GPA/4*100, - grade_weights.get(item.student_grade)/4*100, - int(item.have_taken)*100, + s.GPA/4*100, + grade_weights.get(item.student_grade)/4*100, + int(item.have_taken)*100, int(item.mentor_exp)*100 ) } ) - + studentFeatures.update({s.RIN: studentFeatures_per_course}) @@ -270,26 +269,30 @@ def StartMatch(request): #print(studentPrefs) #studentFeatures = {s: {c: tuple(r.randint(0, 10) for i in range(numFeatures)) for c in classRank} for s, classRank in studentPrefs.items()} #print(studentFeatures) - #classFeatures = {c: (r.randint(3, 10), r.randint(3, 10), r.randint(1, 10), r.randint(1, 10)) for c in classes} - classFeatures = {c: (0, 1000, 5, 5) for c in classes} + classFeatures = {c: (r.randint(3, 10), r.randint(3, 10), r.randint(1, 10), r.randint(1, 10)) for c in classes} + # classFeatures = {c: (0, 1000, 5, 5) for c in classes} matcher = Matcher(studentPrefs, studentFeatures, classCaps, classFeatures) classMatching = matcher.match() - + ''' assert matcher.isStable() print("matching is stable\n") - + ''' #print out some classes and students - for i, (course, student_list) in enumerate(classMatching.items()): + for (course, student_list) in classMatching.items(): print(course) for s in student_list: + this_student = Mentor.objects.filter(RIN = s).first() - query = Grade.objects.filter(student = this_student, course = c) + this_course = Course.objects.filter(name = course).first() + query = Grade.objects.filter(student = this_student, course = this_course) item = query.first() print(" "+s + " cumlative GPA: " + str(this_student.GPA).upper() + " grade: " + item.student_grade.upper() + ", has mentor exp: " + str(item.mentor_exp) ) - + + #print(" ",s , studentFeatures[s][course]) + print() - + unmatchedClasses = set(classes) - classMatching.keys() unmatchedStudents = set(students) - matcher.studentMatching.keys() @@ -299,4 +302,3 @@ def StartMatch(request): print(f"{len(unmatchedClasses)} classes with no students") print(f"{len(unmatchedStudents)} students not in a class") return render(request, 'mentors/index.html', {}) - From 8773fff7706ddb79c76a769a8e4320357b47f799 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Mon, 11 Nov 2019 22:17:33 -0500 Subject: [PATCH 10/44] Fix bugs in the simualtion round --- compsocsite/mentors/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 8d24c630..1b1d75c5 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -203,7 +203,7 @@ def addStudentRandom(request): new_grade = Grade(id=None) #glist = ['a','a-','b+','b','b-','c+','c','c-','d+','d','f','p','n'] #new_grade.id = None - glist = ['a','a-','b+','b','b-','c','c+''n'] + glist = ['a','a-','b+','b','b-','c','c+','n'] new_grade.student_grade = random.choice(glist) if (new_grade.student_grade != 'p' and new_grade.student_grade != 'n' and new_grade.student_grade != 'f'): From afe7e873754bcab00dc2260af13ac55c77510e49 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Wed, 13 Nov 2019 13:13:57 -0500 Subject: [PATCH 11/44] bug fixed --- compsocsite/mentors/forms.py | 91 ++--- compsocsite/mentors/match.py | 2 +- compsocsite/mentors/match_change.py | 370 ++++++++++++++++++ .../mentors/migrations/0001_initial.py | 32 +- .../migrations/0002_auto_20191112_1629.py | 24 ++ .../migrations/0002_remove_mentor_step.py | 17 - compsocsite/mentors/models.py | 170 +++++++- .../mentors/templates/mentors/apply.html | 32 +- .../templates/mentors/apply_preference.html | 2 +- .../templates/mentors/course_feature.html | 43 ++ .../mentors/templates/mentors/index.html | 22 +- compsocsite/mentors/urls.py | 3 +- compsocsite/mentors/views.py | 101 ++++- 13 files changed, 775 insertions(+), 134 deletions(-) create mode 100755 compsocsite/mentors/match_change.py create mode 100644 compsocsite/mentors/migrations/0002_auto_20191112_1629.py delete mode 100644 compsocsite/mentors/migrations/0002_remove_mentor_step.py mode change 100755 => 100644 compsocsite/mentors/templates/mentors/apply_preference.html create mode 100755 compsocsite/mentors/templates/mentors/course_feature.html diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 8752dfbc..2dfd2cb1 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -29,9 +29,12 @@ class Meta: 'email': forms.TextInput(attrs={'placeholder': ' xxx@rpi.email'}), 'phone': forms.TextInput(attrs={'placeholder': ' 5185941234'}), } - def __init__(self, *args, **kwargs): super(MentorApplicationfoForm, self).__init__(*args, **kwargs) + self.fields["course_pref"].required = False + + + self.helper = FormHelper() self.helper.form_id = 'id-exampleForm' self.helper.form_class = 'blueForms' @@ -98,6 +101,7 @@ def __init__(self, *args, **kwargs): Field('recommender', css_class = "inputline"), HTML(""), ), + AccordionGroup('== COMPENSATION AND RESPONSIBILITIES ==', HTML("
      "), HTML(""" @@ -126,7 +130,7 @@ def __init__(self, *args, **kwargs): course_layout, HTML("
      "), ), - + AccordionGroup('== COURSE RANKINGS ==', HTML("
      "), @@ -139,7 +143,8 @@ def __init__(self, *args, **kwargs):
        #{{ forloop.counter }}
        -
      • +
      • {{ course.subject }} {{course.number}}
      @@ -147,9 +152,14 @@ def __init__(self, *args, **kwargs): {% endfor %}
    '''), - HTML(""), + HTML(''' + + + '''), + HTML(""), ), + AccordionGroup('== SCHEDULING ==', HTML("
    "), HTML("Time slot plugin here"), @@ -170,17 +180,24 @@ def save(self, *args, **kwargs): new_applicant.GPA = self.cleaned_data["GPA"] new_applicant.phone = self.cleaned_data["phone"] new_applicant.compensation = self.cleaned_data["compensation"] - new_applicant.save() - print(new_applicant) + # create a dictionary to store a list of preference + pref = Dict() + pref.name = new_applicant.RIN + pref.save() + + new_applicant.course_pref = pref + new_applicant.save() + #orderStr = self.cleaned_data["pref_order"] + # Save Grades on the course average for course in Course.objects.all(): - course_grade = course.name+ "_grade" - course_exp = course.name+ "_exp" + course_grade = course.name + "_grade" + course_exp = course.name + "_exp" - new_grade = Grade() + new_grade = Grade() new_grade.student = new_applicant - new_grade.course = course + new_grade.course = course new_grade.student_grade = self.cleaned_data[course_grade] # Grade on this course if (self.cleaned_data[course_exp] == 'Y'): # Mentor Experience new_grade.mentor_exp = True @@ -195,57 +212,5 @@ def save(self, *args, **kwargs): new_grade.save() print(new_grade.course.name + ": " + new_grade.student_grade.upper()) + return new_applicant -class CompensationForm(ModelForm): - class Meta: - model = Mentor - fields = ('compensation',) - - - -''' -class GradeForm(forms.ModelForm): - class Meta: - model = Grade - fields = ('student_grade', 'mentor_exp') - help_texts= { - 'student_grade': _("Have you taken this course and earned a grade? If so, please specify the grade: "), - 'mentor_exp': _("Have you mentored this class before? "), - } - -class GradeForm2(forms.Form): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - courses = Course.objects.all() - for i in range(len(courses) + 1): - field_name = 'courses_%s' % (i,) - grades = ( - ('a', 'A'), - ('a-', 'A-'), - ('b+', 'B+'), - ('b', 'B'), - ('b-', 'B-'), - ('c+', 'C+'), - ('c', 'C'), - ('c-', 'C-'), - ('d+', 'D+'), - ('d', 'D'), - ('f', 'F'), - ('p', 'Progressing'), - ('n', 'Not Taken'), - ) - - self.fields[field_name] = forms.ChoiceField(choices = grades) - self.initial[field_name] = 'n' - help_texts = { - field_name: _("Have you taken this course and earned a grade? If so, please specify the grade: "), - } - try: - #self.initial[field_name] = 'n' - True - except IndexError: - self.initial[field_name] = '' - # create an extra blank field - # field_name = 'interest_%s' % (i + 1,) - # self.fields[field_name] = forms.CharField(required=False) - ''' \ No newline at end of file diff --git a/compsocsite/mentors/match.py b/compsocsite/mentors/match.py index b8cb6aad..19843def 100755 --- a/compsocsite/mentors/match.py +++ b/compsocsite/mentors/match.py @@ -336,4 +336,4 @@ def isStable(self, studentMatching=None, verbose=False): if verbose: print(f"{c}'s marriage to {s} is unstable:\n{c} prefers {p} over {s} and {p} prefers {c} over her current husband {c2}") return False - return True + return True \ No newline at end of file diff --git a/compsocsite/mentors/match_change.py b/compsocsite/mentors/match_change.py new file mode 100755 index 00000000..b90806f4 --- /dev/null +++ b/compsocsite/mentors/match_change.py @@ -0,0 +1,370 @@ +from collections import defaultdict, deque + +class Matcher: + + #constructs a Matcher instance + + #studentPrefs is a dict from student to class ranking as a list + #e.g. "student1" -> ["class1", "class3", "class2"] + #the ranking doesn't need to be complete + #all missing classes are treated as "worse than nothing" + + #studentFeatures is a dict from student to feature vector + #e.g "student1" -> (0, 1, 3.8) + #the feature vector gives a score for each feature for this student + + #classCaps is a dict from class to maximum capacity + #e.g. "class1" -> 5 + + #classFeatures is a dict from class to feature vector + #e.g. "class1" -> (98, 70, 65) + + #the dot product of a student's feature vector with a particuar class's feature vector is its score + #in terms of that class + def __init__(self, studentPrefs, studentFeatures, classCaps, classFeatures): + + self.studentPrefs = studentPrefs + self.classCaps = classCaps + self.studentFeatures = studentFeatures + self.classFeatures = classFeatures + ''' + #dot product of a and b + def dot(a, b): + #assert len(a) == len(b) + return sum(x*y for x, y in zip(a, b)) + + #returns a complete ranking over students for one specific class + #classFeature is the feature vector for a class + + def makeClassPref(classFeature): + + #return list of students, sorted by dot product, then student name (for tiebreaking) + return sorted(studentPrefs.keys(), key=lambda x: (dot(classFeature, studentFeatures[x]), x), reverse=True) + + #call makeClassPref on each classFeature vector to make the preferences + self.classPrefs = {c: makeClassPref(f) for c, f in classFeatures.items()} + ''' + + #output variables + self.studentMatching = {} #student -> class + self.classMatching = defaultdict(list) #class -> [students] + + #we index preferences at initialization to avoid expensive lookups when matching + self.classRank = defaultdict(dict) #classRank[c][s] is c's ranking of s + self.studentRank = defaultdict(dict) #studentRank[s][c] is s's ranking of c + + #if ranking isn't present, treated as less than none + ''' + for c, prefs in self.classPrefs.items(): + for i, s in enumerate(prefs): + self.classRank[c][s] = i + + for s, prefs in studentPrefs.items(): + for i, c in enumerate(prefs): + self.studentRank[s][c] = i + ''' + + + #Test whether s prefers c over c2. + def prefers(self, s, c, c2): + ranking = self.studentRank[s] + + if c in ranking: + + if c2 in ranking: + #both in ranking + + #return normally + return ranking[c] < ranking[c2] + + else: + #c in ranking, but c2 not in ranking + + #c is preferred + return True + + else: + if c2 in ranking: + #c not in ranking, but c2 in ranking + + #c2 is preferred + return False + + else: + #both not in ranking + + #none, none: false + #strr, none: false + #none, strr: true + #strr, strr: c < c2 + + #None represents no one. it can be thought of as being after the last ranked element, + #and before the unranked ones + + if c2 == None: + #either c is str and c2 is none, or both none + #either way, c is not preferred over c2 + return False + + if c == None: + #c is none and c2 is str + #so c, being no one, is preferred over unranked c2 + return True + + + #if we get here, none isn't involved + #both c and c2 are unranked str's + + #preference based on alphabetical order + return c < c2 + + + #Return the student favored by c after s. + def after(self, c, s): + + #TODO extra checking here might not be needed as classes have full ranking + + if s not in self.classRank[c]: + #TODO will this happen? probably not + print(f"student {s} is not in {c}'s ranking") + #assert False + + #in case it does happen, + #we return the next alphabetical student who is also not ranked + #it is expensive though + + students = sorted(self.studentPrefs.keys()) + + #if we're trying to find the next student after "no one", + #we search from the beginning + if s == None: + i = 0 + + else: + i = students.index(s)+1 + + while i < len(students) and students[i] in self.classRank[c]: + i += 1 + + + if i >= len(students): + #this alg in't perfect. we're mixing what None means + #none usually means "no one" + #but here it means there isn't a next student + #this probably doesn't matter much, as this code will probably not be used + return None + + print(f"using {students[i]}") + return students[i] + + #index of student following s in list of prefs + i = self.classRank[c][s] + 1 + + if i >= len(self.classPrefs[c]): + #no other students are prefered. + return None + + return self.classPrefs[c][i] + + + + #Try to match all classes with their next preferred spouse. + #does class-proposing Gale-Shapely + def match(self): + def dot(a, b): + #assert len(a) == len(b) + return sum(x*y for x, y in zip(a, b)) + + f = True; + studentMatched = {} + for oneStudent in self.studentFeatures: + studentMatched[oneStudent] = False; + + classMatching = {} + for oneClass in self.classFeatures: + classMatching[oneClass] = [] + + while f: + for oneStudent in studentMatched: + if studentMatched[oneStudent]: + continue + if self.studentPrefs[oneStudent] == []: + continue + oneClass = self.studentPrefs[oneStudent].pop(0) + classMatching[oneClass].append(oneStudent) + if len(classMatching[oneClass]) > self.classCaps[oneClass]: + classMatching[oneClass].sort(key=lambda x: dot(self.classFeatures[oneClass], self.studentFeatures[x][oneClass]), reverse=True) + popStudent = classMatching[oneClass].pop(-1) + studentMatched[popStudent] = True + + + # Check if all the classes are full. + f = False; + for oneClass in classMatching: + if len(classMatching[oneClass]) < self.classCaps[oneClass]: + f = True + break + for oneClass in classMatching: + classMatching[oneClass].sort(key=lambda x: dot(self.classFeatures[oneClass], self.studentFeatures[x][oneClass]), reverse=True) + + ''' + #simple combination of a queue with a set + #the set is used for quick "contains" lookups + class SmartQueue: + + def __init__(self): + self.queue = deque() + self.set = set() + + def put(self, x): + self.queue.append(x) + self.set.add(x) + + def pop(self): + x = self.queue.popleft() + self.set.remove(x) + return x + + def __contains__(self, x): + return x in self.set + + def __len__(self): + return len(self.queue) + + + #classes (full ranking) + #students (partial ranking) + + #queue of classes we still have to match + queue = SmartQueue() + + #next is a map from class to next student to propose to + #starts as first preferences + next = {} + + #mapping from students to current class + studentMatching = {} + + #the current capacity for each class + currCap = defaultdict(int) + + #initalize + #we do this in a loop so we only iterate over self.classPrefs once + for c, rank in self.classPrefs.items(): + #we start with all the classes in the queue + queue.put(c) + + #and all the classes will propose to their top ranking student + next[c] = rank[0] + + + #while we still have classes to match + while len(queue) > 0: + + #take class off list + c = queue.pop() + + #make proposals for the remaining capacity + for i in range(self.classCaps[c] - currCap[c]): + + #next student for c to propose to + s = next[c] + + #if we run out of students to propose to + if s == None: + #we break, finished matching, but having less than max capacity + break + + #student after s in c's list of prefs + #"next-next" student for c to propose to + next[c] = self.after(c, s) + + + #if s is already matched + if s in studentMatching: + + #current class that s is in + c2 = studentMatching[s] + + assert c2 != c + + #if s prefers c more than current class + if self.prefers(s, c, c2): + + #old class c2 becomes available again + + #unmatch to old class + currCap[c2] -= 1 + + #if c2 isn't already scheduled to match, put it in the queue + if c2 not in queue: + queue.put(c2) + + #s becomes matched to c + studentMatching[s] = c + currCap[c] += 1 + + + #otherwise, we're rejected + #just go to the next proposal + + #else, s is unmatched, so c gets them + else: + #s becomes matched to c + studentMatching[s] = c + currCap[c] += 1 + + + #we finished the proposals for this class for this round + #now, does this class need another round? + + #if we aren't full, and haven't been "none'd", we re-add ourself + #"none'd" meaning we ran out of students to propose to + if currCap[c] < self.classCaps[c] and next[c] != None: + queue.put(c) + ''' + + #now we've matched all classes, so we're done + + #populate studentMatching + ''' + self.studentMatching = studentMatched + + #populate classMatching from studentMatching + for s, c in studentMatched.items(): + self.classMatching[c].append(s) + ''' + self.classMatching = classMatching + + return self.classMatching + + #check if the mapping of studentMatching to husbands is stable + #TODO this doesn't look at unmatched students or classes. is that a problem? + ''' + def isStable(self, studentMatching=None, verbose=False): + + if studentMatching is None: + studentMatching = self.studentMatching + + for s, c in studentMatching.items(): + + i = self.classRank[c][s] + + preferred = self.classPrefs[c][:i] + + for p in preferred: + + #it's possible p is unmatched + #in that case, c2 is None + c2 = None + if p in studentMatching: + c2 = studentMatching[p] + + #check if p prefers us over current matching + #if c2 is none, this just checks if p prefers us to nobody + if self.prefers(p, c, c2): + if verbose: + print(f"{c}'s marriage to {s} is unstable:\n{c} prefers {p} over {s} and {p} prefers {c} over her current husband {c2}") + return False + return True + ''' diff --git a/compsocsite/mentors/migrations/0001_initial.py b/compsocsite/mentors/migrations/0001_initial.py index 5ae132cd..4b24983e 100644 --- a/compsocsite/mentors/migrations/0001_initial.py +++ b/compsocsite/mentors/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.1 on 2019-11-02 17:13 +# Generated by Django 2.2.1 on 2019-11-12 21:21 import django.core.validators from django.db import migrations, models @@ -28,11 +28,26 @@ class Migration(migrations.Migration): ('feature_mentor_exp', models.IntegerField(default=0)), ], ), + migrations.CreateModel( + name='Dict', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ], + ), + migrations.CreateModel( + name='Professor', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_name', models.CharField(max_length=50)), + ('last_name', models.CharField(max_length=50)), + ('department', models.CharField(max_length=50)), + ], + ), migrations.CreateModel( name='Mentor', fields=[ ('applied', models.BooleanField(default=False)), - ('step', models.IntegerField(default=1)), ('RIN', models.CharField(max_length=9, primary_key=True, serialize=False, validators=[django.core.validators.MinLengthValidator(9)])), ('first_name', models.CharField(max_length=50)), ('last_name', models.CharField(max_length=50)), @@ -41,15 +56,16 @@ class Migration(migrations.Migration): ('phone', models.CharField(max_length=50)), ('recommender', models.CharField(max_length=50)), ('compensation', models.CharField(choices=[('1', 'Pay'), ('2', 'Credit'), ('3', 'No Preference')], default='1', max_length=1)), + ('course_pref', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='mentors.Dict')), ], ), migrations.CreateModel( - name='Professor', + name='KeyValuePair', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_name', models.CharField(max_length=50)), - ('last_name', models.CharField(max_length=50)), - ('department', models.CharField(max_length=50)), + ('key', models.CharField(db_index=True, max_length=240)), + ('value', models.CharField(db_index=True, max_length=240)), + ('container', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Dict')), ], ), migrations.CreateModel( @@ -59,8 +75,8 @@ class Migration(migrations.Migration): ('student_grade', models.CharField(choices=[('a', 'A'), ('a-', 'A-'), ('b+', 'B+'), ('b', 'B'), ('b-', 'B-'), ('c+', 'C+'), ('c', 'C'), ('c-', 'C-'), ('d+', 'D+'), ('d', 'D'), ('f', 'F'), ('p', 'Progressing'), ('n', 'Not Taken')], default='n', max_length=1)), ('have_taken', models.BooleanField(default=False)), ('mentor_exp', models.BooleanField(default=False)), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Course')), - ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Mentor')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='mentors.Course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='mentors.Mentor')), ], ), ] diff --git a/compsocsite/mentors/migrations/0002_auto_20191112_1629.py b/compsocsite/mentors/migrations/0002_auto_20191112_1629.py new file mode 100644 index 00000000..63a5d1ef --- /dev/null +++ b/compsocsite/mentors/migrations/0002_auto_20191112_1629.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.1 on 2019-11-12 21:29 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='grade', + name='course', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Course'), + ), + migrations.AlterField( + model_name='grade', + name='student', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Mentor'), + ), + ] diff --git a/compsocsite/mentors/migrations/0002_remove_mentor_step.py b/compsocsite/mentors/migrations/0002_remove_mentor_step.py deleted file mode 100644 index 95cc69b6..00000000 --- a/compsocsite/mentors/migrations/0002_remove_mentor_step.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-09 20:04 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='mentor', - name='step', - ), - ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index a67b3cb7..87cb9696 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -5,7 +5,6 @@ from django.contrib.auth.models import User from django.core.validators import MinLengthValidator - @python_2_unicode_compatible # a helper function to ensure min and max value @@ -19,6 +18,165 @@ def formfield(self, **kwargs): defaults.update(kwargs) return super(MinMaxFloat, self).formfield(**defaults) + + +# https://djangosnippets.org/snippets/2451/ +# unmodified version of Dict +class Dict(models.Model): + """A model that represents a Dict. This model implements most of the Dict interface, + allowing it to be used like a python Dict. + + """ + name = models.CharField(max_length = 1000) + + @staticmethod + def getDict(name): + """Get the Dict of the given name. + + """ + df = Dict.objects.select_related().get(name=name) + + return df + + def __getitem__(self, key): + """Returns the value of the selected key. + + """ + return self.keyvaluepair_set.get(key=key).value + + def __setitem__(self, key, value): + """Sets the value of the given key in the Dict. + + """ + try: + kvp = self.keyvaluepair_set.get(key=key) + + except KeyValuePair.DoesNotExist: + KeyValuePair.objects.create(container=self, key=key, value=value) + + else: + kvp.value = value + kvp.save() + + def __delitem__(self, key): + """Removed the given key from the Dict. + + """ + try: + kvp = self.keyvaluepair_set.get(key=key) + + except KeyValuePair.DoesNotExist: + raise KeyError + + else: + kvp.delete() + + def __len__(self): + """Returns the length of this Dict. + + """ + return self.keyvaluepair_set.count() + + def iterkeys(self): + """Returns an iterator for the keys of this Dict. + + """ + return iter(kvp.key for kvp in self.keyvaluepair_set.all()) + + def itervalues(self): + """Returns an iterator for the keys of this Dict. + + """ + return iter(kvp.value for kvp in self.keyvaluepair_set.all()) + + __iter__ = iterkeys + + def iteritems(self): + """Returns an iterator over the tuples of this Dict. + + """ + return iter((kvp.key, kvp.value) for kvp in self.keyvaluepair_set.all()) + + def keys(self): + """Returns all keys in this Dict as a list. + + """ + return [kvp.key for kvp in self.keyvaluepair_set.all()] + + def values(self): + """Returns all values in this Dict as a list. + + """ + return [kvp.value for kvp in self.keyvaluepair_set.all()] + + def items(self): + """Get a list of tuples of key, value for the items in this Dict. + This is modeled after dict.items(). + + """ + return [(kvp.key, kvp.value) for kvp in self.keyvaluepair_set.all()] + + def get(self, key, default=None): + """Gets the given key from the Dict. If the key does not exist, it + returns default. + + """ + try: + return self[key] + + except KeyError: + return default + + def has_key(self, key): + """Returns true if the Dict has the given key, false if not. + + """ + return self.contains(key) + + def contains(self, key): + """Returns true if the Dict has the given key, false if not. + + """ + try: + self.keyvaluepair_set.get(key=key) + return True + + except KeyValuePair.DoesNotExist: + return False + + def clear(self): + """Deletes all keys in the Dict. + + """ + self.keyvaluepair_set.all().delete() + + def __unicode__(self): + """Returns a unicode representation of the Dict. + + """ + return unicode(self.asPyDict()) + + def asPyDict(self): + """Get a python Dict that represents this Dict object. + This object is read-only. + + """ + fieldDict = dict() + + for kvp in self.keyvaluepair_set.all(): + fieldDict[kvp.key] = kvp.value + + return fieldDict + + +class KeyValuePair(models.Model): + """A Key-Value pair with a pointer to the Dict that owns it. + + """ + container = models.ForeignKey(Dict, db_index=True, on_delete = models.CASCADE) + key = models.CharField(max_length=240, db_index=True) + value = models.CharField(max_length=240, db_index=True) + # the model for a mentor applicant class Mentor(models.Model): @@ -43,11 +201,11 @@ class Mentor(models.Model): ('2', 'Credit'), ('3', 'No Preference'), ) - compensation = models.CharField(max_length=1, choices=compensation_choice, default='1') - + compensation = models.CharField(max_length=1, choices = compensation_choice, default='1') + course_pref = models.ForeignKey(Dict, on_delete = models.CASCADE, default = None) def __str__(self): - return "" + return self.first_name + " " + self.last_name # The model for a course class Course(models.Model): @@ -61,6 +219,8 @@ class Course(models.Model): feature_course_GPA = models.IntegerField(default=0) feature_mentor_exp = models.IntegerField(default=0) + def __str__(self): + return self.subject + " " + self.number + " " + self.name class Grade(models.Model): student = models.ForeignKey(Mentor, on_delete=models.CASCADE) @@ -93,3 +253,5 @@ class Professor(models.Model): last_name = models.CharField(max_length=50) department = models.CharField(max_length=50) + def __str__(self): + return self.first_name + " " + self.last_name \ No newline at end of file diff --git a/compsocsite/mentors/templates/mentors/apply.html b/compsocsite/mentors/templates/mentors/apply.html index 0a52153d..c50031c8 100755 --- a/compsocsite/mentors/templates/mentors/apply.html +++ b/compsocsite/mentors/templates/mentors/apply.html @@ -17,25 +17,23 @@ {% endif %}

    - {% if not applied %} +{% if not applied %} - -
    - -
    -
    - Mentor application Form -
    +
    +
    +
    + Mentor application Form +
    -
    - {% load crispy_forms_tags %} - {% crispy apply_form apply_form.helper %} - {{ form.errors }} -
    +
    + {% load crispy_forms_tags %} + {% crispy apply_form apply_form.helper %} + {{ form.errors }}
    - {% else %} -
    Session Expired
    - {% endif %} - +
    +
    +{% else %} +
    Session Expired
    +{% endif %} {% endif %} {% endblock %} diff --git a/compsocsite/mentors/templates/mentors/apply_preference.html b/compsocsite/mentors/templates/mentors/apply_preference.html old mode 100755 new mode 100644 index 1ac98c26..1e4ea39e --- a/compsocsite/mentors/templates/mentors/apply_preference.html +++ b/compsocsite/mentors/templates/mentors/apply_preference.html @@ -440,7 +440,7 @@
    #{{ forloop.counter }}
    {% for selection in s %}
  • - {{course.name}} + {{ course.name }}
  • {% endfor %} diff --git a/compsocsite/mentors/templates/mentors/course_feature.html b/compsocsite/mentors/templates/mentors/course_feature.html new file mode 100755 index 00000000..25395b7b --- /dev/null +++ b/compsocsite/mentors/templates/mentors/course_feature.html @@ -0,0 +1,43 @@ +{% extends 'mentors/apply.html' %} +{% load staticfiles %} +{% block content %} + +
    +
    +
    +
    + +
    + {% csrf_token %} + Choose Course: + + + +
    +
      +
    • + Cumlative GPA :
      0
      +
      +
    • +
    • + Grade on this course before :
      0
      +
      +
    • +
    • + Have Mentor Experience :
      0
      +
      +
    • +
    +
    + +
    + +
    +
    +
    +
    +{% endblock %} diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index 2eacfec7..e1706704 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -15,6 +15,8 @@ {% endif %}

    + +
    @@ -39,8 +41,24 @@
    -
    + + +
    +
    +
    + Professor Interface +
    + +
    + Check your courses: + View + +
    +
    +
    + +
    @@ -52,7 +70,7 @@

    Add Course (DEV)

    {% csrf_token %} -

    Add Student Random

    +

    Add Student Random (DEV)

    diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index fd9e0623..5f2502e1 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -12,10 +12,11 @@ url(r'^apply$', login_required(views.ApplyView.as_view()), name='apply'), # personal info url(r'^apply/$', views.applystep, name='applyfunc1'), + url(r'^viewcourse$', login_required(views.CourseFeatureView.as_view()), name='viewcourse'), + # compensation and responsbility #url(r'^applyfunc2/$', views.applystep, name='applyfunc2'), # RANKING of preference of course of studnet - url(r'^apply/$', views.applystep, name='applyfunc3'), url(r'^addcoursefunc/$', views.addcourse, name='addcoursefunc'), url(r'^addStudentRandomfunc/$', views.addStudentRandom, name='addStudentRandomfunc'), diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 1b1d75c5..af9fde0f 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -39,6 +39,7 @@ import logging import random as r from .match import Matcher +import ast class IndexView(views.generic.ListView): """ @@ -128,22 +129,38 @@ def get_context_data(self, **kwargs): ctx['applied'] = Mentor.applied ctx['applications'] = Mentor.objects.all() ctx['step'] = Mentor.step - # === need more for course data === - return ctx def get_queryset(self): return Mentor.objects.all() +# view course features and change their weights +class CourseFeatureView(views.generic.ListView): + template_name = 'mentors/course_feature.html' + def get_context_data(self, **kwargs): + ctx = super(CourseFeatureView, self).get_context_data(**kwargs) + ctx['courses'] = Course.objects.all() + return ctx + def get_queryset(self): + return Course.objects.all() + # apply step def applystep(request): if request.method == 'POST': # initate a new mentor applicant form = MentorApplicationfoForm(request.POST) if form.is_valid(): - form.save() - print("yes") + new_applicant = form.save() + order_str = breakties(request.POST['pref_order']) + + pref = new_applicant.course_pref + pref[new_applicant.RIN] = order_str + pref.save() + + l = pref[new_applicant.RIN] + l = [n.strip() for n in ast.literal_eval(l)] # convert str list to actual list u['a', 'b', 'c'] -> ['a', 'b', 'c'] + print(len(l)) else: print(form.errors) @@ -159,6 +176,17 @@ def applystep(request): return render(request, 'mentors/apply.html', {'courses': courses, 'apply_form': form}) +# return the prefer after brutally break ties +def breakties(order_str): + l = [] + order = getPrefOrder(order_str) # change str to double lists + for i in range(len(order)): + random.shuffle(order[i]) # break ties with random + for j in range(len(order[i])): + l.append(order[i][j]) + + return l + # withdraw application, should add semester later def withdraw(request): if request.method == 'GET': @@ -166,7 +194,7 @@ def withdraw(request): Mentor.applied = False return HttpResponseRedirect(reverse('mentors:index')) - +# load CS_Course.csv def addcourse(request): if request.method == 'POST': Course.objects.all().delete() @@ -184,8 +212,11 @@ def addcourse(request): return render(request, 'mentors/index.html', {}) -# Randomly add students +# Randomly add students with assigned numbers def addStudentRandom(request): + classes = [course.name for course in Course.objects.all()] + numClass = len(Course.objects.all()) + if request.method == 'POST': Mentor.objects.all().delete() @@ -195,14 +226,19 @@ def addStudentRandom(request): new_applicant.RIN = str(661680900 + i) new_applicant.first_name = "student_" new_applicant.last_name = str(i) - new_applicant.GPA = round(random.uniform(2.5, 4)*100)/100 # simple round + new_applicant.GPA = round(random.uniform(2.0, 4)*100)/100 # simple round new_applicant.phone = 518596666 + + pref = Dict() + pref.name = new_applicant.RIN + pref.save() + new_applicant.course_pref = pref new_applicant.save() + new_applicant.course_pref[new_applicant.RIN] = r.sample(classes, r.randint(1, numClass)) for course in Course.objects.all(): new_grade = Grade(id=None) #glist = ['a','a-','b+','b','b-','c+','c','c-','d+','d','f','p','n'] - #new_grade.id = None glist = ['a','a-','b+','b','b-','c','c+','n'] new_grade.student_grade = random.choice(glist) @@ -213,8 +249,6 @@ def addStudentRandom(request): new_grade.have_taken = False new_grade.mentor_exp = False - #new_grade.have_taken = random.choice([True, False]) - #new_grade.mentor_exp = random.choice([True, False]) new_grade.course = course new_grade.student = new_applicant new_grade.save() @@ -255,7 +289,6 @@ def StartMatch(request): ) } ) - studentFeatures.update({s.RIN: studentFeatures_per_course}) @@ -264,33 +297,34 @@ def StartMatch(request): classCaps = {c: r.randint(3, 10) for c in classes} students = [studnet.RIN for studnet in Mentor.objects.all()] numClasses = len(Course.objects.all()) + studentPrefs = {s.RIN: [n.strip() for n in ast.literal_eval(s.course_pref[s.RIN])] for s in Mentor.objects.all()} - studentPrefs = {s: [c for c in r.sample(classes, r.randint(numClasses, numClasses ))] for s in students} + + #studentPrefs = {s: [c for c in r.sample(classes, r.randint(numClasses, numClasses ))] for s in students} #print(studentPrefs) #studentFeatures = {s: {c: tuple(r.randint(0, 10) for i in range(numFeatures)) for c in classRank} for s, classRank in studentPrefs.items()} #print(studentFeatures) classFeatures = {c: (r.randint(3, 10), r.randint(3, 10), r.randint(1, 10), r.randint(1, 10)) for c in classes} - # classFeatures = {c: (0, 1000, 5, 5) for c in classes} + #classFeatures = {c: (0, 1000, 5, 5) for c in classes} matcher = Matcher(studentPrefs, studentFeatures, classCaps, classFeatures) classMatching = matcher.match() + ''' assert matcher.isStable() print("matching is stable\n") ''' + #print out some classes and students for (course, student_list) in classMatching.items(): - print(course) - for s in student_list: - - this_student = Mentor.objects.filter(RIN = s).first() + print(course + ", cap: " + str(classCaps[course]) + ", features: ", classFeatures[course]) + for s_rin in student_list: + this_student = Mentor.objects.filter(RIN = s_rin).first() this_course = Course.objects.filter(name = course).first() query = Grade.objects.filter(student = this_student, course = this_course) item = query.first() - print(" "+s + " cumlative GPA: " + str(this_student.GPA).upper() + " grade: " + item.student_grade.upper() + ", has mentor exp: " + str(item.mentor_exp) ) - + print(" "+s_rin + " cumlative GPA: " + str(this_student.GPA).upper() + " grade: " + item.student_grade.upper() + ", has mentor exp: " + str(item.mentor_exp) ) #print(" ",s , studentFeatures[s][course]) - print() unmatchedClasses = set(classes) - classMatching.keys() @@ -302,3 +336,30 @@ def StartMatch(request): print(f"{len(unmatchedClasses)} classes with no students") print(f"{len(unmatchedStudents)} students not in a class") return render(request, 'mentors/index.html', {}) + +# function to get preference order from a string +# String orderStr +# Question question +# return List> prefOrder +def getPrefOrder(orderStr): + # empty string + if orderStr == "": + return None + if ";;|;;" in orderStr: + current_array = orderStr.split(";;|;;") + final_order = [] + length = 0 + for item in current_array: + if item != "": + curr = item.split(";;") + final_order.append(curr) + length += len(curr) + else: + final_order = json.loads(orderStr) + + # the user hasn't ranked all the preferences yet + #if length != len(question.item_set.all()): + # return None + + return final_order + From f665a6461c21e47343d4aec361d019d56eadc028 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Wed, 13 Nov 2019 13:15:07 -0500 Subject: [PATCH 12/44] bug fixed --- compsocsite/compsocsite/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compsocsite/compsocsite/urls.py b/compsocsite/compsocsite/urls.py index 70b2ab15..6e8e0f52 100755 --- a/compsocsite/compsocsite/urls.py +++ b/compsocsite/compsocsite/urls.py @@ -34,6 +34,7 @@ url(r'^sessions/', include('sessions_local.urls')), url(r'^static/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT, 'show_indexes':True}), url(r'^multipolls/', include('multipolls.urls')), + url(r'^mentors/', include('mentors.urls')), url(r'^GM2017$', GMView.as_view(), name='voting_demo'), url(r'^message$', sendMessage, name='message'), url(r'^GM2017$', GMView.as_view(), name='GM_2017'), From 8ef8037504e5dcdd60fe105dd932fc400a401c1d Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Wed, 13 Nov 2019 16:34:07 -0500 Subject: [PATCH 13/44] fix bugs of form --- compsocsite/mentors/forms.py | 4 ++-- compsocsite/mentors/templates/mentors/index.html | 2 +- compsocsite/mentors/views.py | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 2dfd2cb1..72c1eb07 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -206,9 +206,9 @@ def save(self, *args, **kwargs): # if the student has failed the class, or is progressing, we consider he did not take the course if (new_grade.student_grade != 'p' and new_grade.student_grade != 'n' and new_grade.student_grade != 'f'): - new_grade.have_taken = False - else: new_grade.have_taken = True + else: + new_grade.have_taken = False new_grade.save() print(new_grade.course.name + ": " + new_grade.student_grade.upper()) diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index e1706704..298a388c 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -35,7 +35,7 @@ {% else %} - Begin to apply: + Begin to apply: Apply {% endif %} diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index af9fde0f..22c5794b 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -160,7 +160,7 @@ def applystep(request): l = pref[new_applicant.RIN] l = [n.strip() for n in ast.literal_eval(l)] # convert str list to actual list u['a', 'b', 'c'] -> ['a', 'b', 'c'] - print(len(l)) + #print(len(l)) else: print(form.errors) @@ -218,12 +218,12 @@ def addStudentRandom(request): numClass = len(Course.objects.all()) if request.method == 'POST': - Mentor.objects.all().delete() + #Mentor.objects.all().delete() num_students = request.POST['num_students'] for i in range(int(num_students)): new_applicant = Mentor() - new_applicant.RIN = str(661680900 + i) + new_applicant.RIN = str(100000000 + i) new_applicant.first_name = "student_" new_applicant.last_name = str(i) new_applicant.GPA = round(random.uniform(2.0, 4)*100)/100 # simple round @@ -278,6 +278,7 @@ def StartMatch(request): studentFeatures = {} for s in Mentor.objects.all(): studentFeatures_per_course = {} + #print(s) for c in Course.objects.all(): item = Grade.objects.filter(student = s, course = c).first() studentFeatures_per_course.update( @@ -289,6 +290,7 @@ def StartMatch(request): ) } ) + #print(studentFeatures_per_course) studentFeatures.update({s.RIN: studentFeatures_per_course}) From c112c6ba6931f8f76eb376ced44e3e63fe02ead9 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Thu, 14 Nov 2019 13:46:25 -0500 Subject: [PATCH 14/44] Redirect the matching result to the HTML page --- compsocsite/mentors/forms.py | 17 +- .../mentors/migrations/0001_initial.py | 9 +- .../migrations/0002_auto_20191112_1629.py | 24 - .../migrations/0002_auto_20191113_2042.py | 18 + compsocsite/mentors/models.py | 51 +- .../templates/mentors/apply_compensation.html | 42 -- .../mentors/apply_personal_info.html | 13 - .../templates/mentors/apply_preference.html | 447 ------------------ .../mentors/templates/mentors/index.html | 2 +- .../templates/mentors/view_match_result.html | 35 ++ compsocsite/mentors/urls.py | 3 +- compsocsite/mentors/views.py | 79 ++-- 12 files changed, 148 insertions(+), 592 deletions(-) delete mode 100644 compsocsite/mentors/migrations/0002_auto_20191112_1629.py create mode 100644 compsocsite/mentors/migrations/0002_auto_20191113_2042.py delete mode 100755 compsocsite/mentors/templates/mentors/apply_compensation.html delete mode 100755 compsocsite/mentors/templates/mentors/apply_personal_info.html delete mode 100644 compsocsite/mentors/templates/mentors/apply_preference.html create mode 100755 compsocsite/mentors/templates/mentors/view_match_result.html diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 72c1eb07..24f63332 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -11,7 +11,16 @@ class MentorApplicationfoForm(ModelForm): class Meta: model = Mentor - fields = '__all__' + #fields = '__all__' + fields = ( 'RIN', + 'first_name', + 'last_name', + 'GPA', + 'email', + 'phone', + 'recommender', + 'compensation', + ) ''' help_texts = { 'RIN': _(' *Required'), @@ -31,10 +40,8 @@ class Meta: } def __init__(self, *args, **kwargs): super(MentorApplicationfoForm, self).__init__(*args, **kwargs) - self.fields["course_pref"].required = False + #self.fields["course_pref"].required = False - - self.helper = FormHelper() self.helper.form_id = 'id-exampleForm' self.helper.form_class = 'blueForms' @@ -77,9 +84,7 @@ def __init__(self, *args, **kwargs): course_layout.append(Field(course.name+ "_grade", label_class = "long_label")) #course_layout.append(HTML('

    ')) - course_layout.append(InlineRadios(course.name+ "_exp")) - course_layout.append(HTML("
    ")) # Time slot choices diff --git a/compsocsite/mentors/migrations/0001_initial.py b/compsocsite/mentors/migrations/0001_initial.py index 4b24983e..f8606291 100644 --- a/compsocsite/mentors/migrations/0001_initial.py +++ b/compsocsite/mentors/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.1 on 2019-11-12 21:21 +# Generated by Django 2.2.1 on 2019-11-14 00:42 import django.core.validators from django.db import migrations, models @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='Professor', + name='Instrcutor', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('first_name', models.CharField(max_length=50)), @@ -57,6 +57,7 @@ class Migration(migrations.Migration): ('recommender', models.CharField(max_length=50)), ('compensation', models.CharField(choices=[('1', 'Pay'), ('2', 'Credit'), ('3', 'No Preference')], default='1', max_length=1)), ('course_pref', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='mentors.Dict')), + ('mentor_course', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='mentors.Course')), ], ), migrations.CreateModel( @@ -75,8 +76,8 @@ class Migration(migrations.Migration): ('student_grade', models.CharField(choices=[('a', 'A'), ('a-', 'A-'), ('b+', 'B+'), ('b', 'B'), ('b-', 'B-'), ('c+', 'C+'), ('c', 'C'), ('c-', 'C-'), ('d+', 'D+'), ('d', 'D'), ('f', 'F'), ('p', 'Progressing'), ('n', 'Not Taken')], default='n', max_length=1)), ('have_taken', models.BooleanField(default=False)), ('mentor_exp', models.BooleanField(default=False)), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='mentors.Course')), - ('student', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='mentors.Mentor')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Mentor')), ], ), ] diff --git a/compsocsite/mentors/migrations/0002_auto_20191112_1629.py b/compsocsite/mentors/migrations/0002_auto_20191112_1629.py deleted file mode 100644 index 63a5d1ef..00000000 --- a/compsocsite/mentors/migrations/0002_auto_20191112_1629.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-12 21:29 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='grade', - name='course', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Course'), - ), - migrations.AlterField( - model_name='grade', - name='student', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mentors.Mentor'), - ), - ] diff --git a/compsocsite/mentors/migrations/0002_auto_20191113_2042.py b/compsocsite/mentors/migrations/0002_auto_20191113_2042.py new file mode 100644 index 00000000..812466b8 --- /dev/null +++ b/compsocsite/mentors/migrations/0002_auto_20191113_2042.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-11-14 01:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='mentor', + old_name='mentor_course', + new_name='mentored_course', + ), + ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index 87cb9696..2b06aa04 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -177,24 +177,41 @@ class KeyValuePair(models.Model): key = models.CharField(max_length=240, db_index=True) value = models.CharField(max_length=240, db_index=True) -# the model for a mentor applicant +# The model for a course +class Course(models.Model): + subject = models.CharField(max_length=4) # e.g CSCI + number = models.CharField(max_length=4, default="1000") # e.g 1100 + name = models.CharField(max_length=50, default = 'none') # e.g Intro to programming + + # This should change to foreignkey afterwards + # But we left this now to make the implementation easy + instructor = models.CharField(max_length=50, default = 'none') # Instrcutor's name + + # feature weights to represent the pref + feature_cumlative_GPA = models.IntegerField(default=0) + feature_has_taken = models.IntegerField(default=0) + feature_course_GPA = models.IntegerField(default=0) + feature_mentor_exp = models.IntegerField(default=0) + + + def __str__(self): + return self.subject + " " + self.number + " " + self.name + +# the model to represent a mentor applicant class Mentor(models.Model): # already applied for this semester applied = models.BooleanField(default = False) #step = models.IntegerField(default = 1) - # Personal Info of applicants + # Personal Info & preference of applicants RIN = models.CharField(max_length=9, validators=[MinLengthValidator(9)], primary_key=True) first_name = models.CharField(max_length=50) # first name last_name = models.CharField(max_length=50) # last name - GPA = MinMaxFloat(min_value = 0.0, max_value = 4.0) - email = models.CharField(max_length=50) phone = models.CharField(max_length=50) # ??? recommender = models.CharField(max_length=50) - # Compensation Choices compensation_choice = ( ('1', 'Pay'), @@ -202,25 +219,21 @@ class Mentor(models.Model): ('3', 'No Preference'), ) compensation = models.CharField(max_length=1, choices = compensation_choice, default='1') + + # Course preference of applicants, the data model here is dictionary + # Yeah we can do charfield... will change it afterwards course_pref = models.ForeignKey(Dict, on_delete = models.CASCADE, default = None) + # Many to one relation + # mentor_course -> {s1, s2, s3, ...} + # To get all the mentors in a course: course.mentor_set.all() + mentored_course = models.ForeignKey(Course, on_delete = models.CASCADE, default = None, null=True) + + def __str__(self): return self.first_name + " " + self.last_name -# The model for a course -class Course(models.Model): - subject = models.CharField(max_length=4) # e.g CSCI - number = models.CharField(max_length=4, default="1000") # e.g 1100 - name = models.CharField(max_length=50, default = 'none') # e.g Intro to programming - instructor = models.CharField(max_length=50, default = 'none') # Instrcutor's name - - feature_cumlative_GPA = models.IntegerField(default=0) - feature_has_taken = models.IntegerField(default=0) - feature_course_GPA = models.IntegerField(default=0) - feature_mentor_exp = models.IntegerField(default=0) - def __str__(self): - return self.subject + " " + self.number + " " + self.name class Grade(models.Model): student = models.ForeignKey(Mentor, on_delete=models.CASCADE) @@ -248,7 +261,7 @@ class Grade(models.Model): -class Professor(models.Model): +class Instrcutor(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) department = models.CharField(max_length=50) diff --git a/compsocsite/mentors/templates/mentors/apply_compensation.html b/compsocsite/mentors/templates/mentors/apply_compensation.html deleted file mode 100755 index 4f4ca82e..00000000 --- a/compsocsite/mentors/templates/mentors/apply_compensation.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends 'mentors/apply.html' %} -{% load staticfiles %} - - - -{% block content %} -
    - {% csrf_token %} - Compensation and Responsibilities - -
    We currently have funding for a fixed number of paid programming mentors. Therefore, if you apply for pay, you may be asked to work for credit instead. Course credit counts as a graded 1-credit or 2-credit free elective. In general, if you work an average of 1-3 hours per week, you will receive 1 credit; if you average 4 or more hours per week, you will receive 2 credits. Note that no more than 4 credits may be earned as an undergraduate (spanning all courses and all semesters). Please do not apply for credit if you have already earned 4 credits as a mentor; apply only for pay. -
    - -
    - -
    - - Pay ($14/hour)   - Course Credit   - No Preference -   « required -
    -
    - - I have been employed and paid by RPI before -
    - -
    - - -
    For a paid position, you must follow the given instructions to ensure you are paid; otherwise, another candidate will be selected. More specifically, you will be required to have a student employment card. This requires you to have a federal I-9 on file. Bring appropriate identification with you to Amos Eaton 109 if this is your first time working for RPI; click here for a list of acceptable documents. Note that original documents are required; copies cannot be accepted. -
    -
    Detailed instructions are listed here: http://finance.rpi.edu/update.do?catcenterkey=184
    -
    Please note that to be paid, you will be required to submit your specific hours in SIS every two weeks. This must be completed to get paid on time, and any late submissions will incur a fee charged to the department.
    -
    If you are unable to attend your assigned labs or office hours (e.g., illness, interview, conference, etc.), you must notify your graduate lab TA and instructor and help arrange a substitute mentor. Unexcused absences will cause you to either earn a lower letter grade or not be asked to mentor again.
    - -
    - -
    -
    - -{% endblock %} \ No newline at end of file diff --git a/compsocsite/mentors/templates/mentors/apply_personal_info.html b/compsocsite/mentors/templates/mentors/apply_personal_info.html deleted file mode 100755 index 2c8efb5d..00000000 --- a/compsocsite/mentors/templates/mentors/apply_personal_info.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends 'mentors/apply.html' %} -{% load staticfiles %} - - - -{% block content %} -
    - Personal Information - {% csrf_token %} - {{ apply_form.as_p }} -
    - -{% endblock %} \ No newline at end of file diff --git a/compsocsite/mentors/templates/mentors/apply_preference.html b/compsocsite/mentors/templates/mentors/apply_preference.html deleted file mode 100644 index 1e4ea39e..00000000 --- a/compsocsite/mentors/templates/mentors/apply_preference.html +++ /dev/null @@ -1,447 +0,0 @@ -{% extends 'mentors/apply.html' %} -{% load staticfiles %} - - - -{% block content %} -
    - {% csrf_token %} - - - -
    - Course Selections -
    - Below is a list of courses likely to need mentors. - Please check all courses for which you are able to mentor. - Note that - CSCI 1010 and CSCI 1100 are in Python, - CSCI 1190 is in MATLAB, - CSCI 1200 is in C++, - CSCI 2300 is in Python/C++, - CSCI 2500 is in C/Assembly, - and CSCI 2600 is in Java. - For each of the courses you have taken, please specify the letter grade you earned in the course at RPI (or select "AP" if you earned AP credit for CSCI 1100). -
    -
    - Note that you cannot be a mentor for a course you will be taking in the same semester or for a course you plan to take in the future. - You are however allowed to mentor for courses you have never taken; - be sure to describe equivalent courses or experiences in the general comments textbox below. -
    - -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    - -
    - -         - I am qualified to mentor this course -
    -         - I have taken this course at RPI before and earned grade: - -
    -         - I have mentored this RPI course before -
    -
    -
    - - -{% endblock %} -
      - {% if currentSelection %} - -
      - {% for course in courses %} - {% if course %} -
        - -
        #{{ forloop.counter }}
        - {% for selection in s %} -
      • - {{ course.name }} -
      • - {% endfor %} - -
      \ No newline at end of file diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index 298a388c..c178561e 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -52,7 +52,7 @@
      Check your courses: - View + View
    diff --git a/compsocsite/mentors/templates/mentors/view_match_result.html b/compsocsite/mentors/templates/mentors/view_match_result.html new file mode 100755 index 00000000..ff63e281 --- /dev/null +++ b/compsocsite/mentors/templates/mentors/view_match_result.html @@ -0,0 +1,35 @@ +{% extends 'polls/base.html' %} + +{% block content %} +{% if user.is_authenticated %} + +
    +

    + {% if messages %} +

      + {% for message in messages %} +
      + {{ message }} +
      + {% endfor %} +
    + {% endif %} +

    +
    + +
    +
    +
    + {% for course in result.courses %} +
    {{course.name}}
    + {% for mentor in course.mentors %} +
      {{mentor.name}}, GPA: {{mentor.GPA}}, grade: {{mentor.grade}}, mentor experience: {{mentor.Exp}}
    + {% endfor %} +

    + {% endfor %} +
    +
    +
    + +{% endif %} +{% endblock %} diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index 5f2502e1..867fe849 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -12,7 +12,8 @@ url(r'^apply$', login_required(views.ApplyView.as_view()), name='apply'), # personal info url(r'^apply/$', views.applystep, name='applyfunc1'), - url(r'^viewcourse$', login_required(views.CourseFeatureView.as_view()), name='viewcourse'), + url(r'^view-course$', login_required(views.CourseFeatureView.as_view()), name='view-course'), + url(r'^view-course-result$', login_required(views.MatchResultView.as_view()), name='view-course-result'), # compensation and responsbility #url(r'^applyfunc2/$', views.applystep, name='applyfunc2'), diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 22c5794b..c9e6d2e3 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -24,7 +24,7 @@ from prefpy.gmm_mixpl import * from prefpy.egmm_mixpl import * from django.conf import settings -from multipolls.models import * +from django.template import Context from django.http import HttpResponseRedirect from django.shortcuts import render @@ -145,6 +145,16 @@ def get_context_data(self, **kwargs): def get_queryset(self): return Course.objects.all() +class MatchResultView(views.generic.ListView): + template_name = 'mentors/view_match_result.html' + def get_context_data(self, **kwargs): + ctx = super(MatchResultView, self).get_context_data(**kwargs) + ctx['courses'] = Course.objects.all() + return ctx + def get_queryset(self): + return Course.objects.all() + + # apply step def applystep(request): if request.method == 'POST': @@ -160,16 +170,16 @@ def applystep(request): l = pref[new_applicant.RIN] l = [n.strip() for n in ast.literal_eval(l)] # convert str list to actual list u['a', 'b', 'c'] -> ['a', 'b', 'c'] - #print(len(l)) + + return render(request, 'mentors/index.html', {'applied': True}) else: print(form.errors) else: # display empty form - print("empty") form = MentorApplicationfoForm() #Mentor.applied = True - #Mentor.step += 1 + #return HttpResponseRedirect(reverse('groups:members', args=(group.id,))) courses = Course.objects.all() @@ -260,19 +270,11 @@ def addStudentRandom(request): def StartMatch(request): if request.method == 'POST': - grade_weights = { 'a': 4, - 'a-': 3.69, - 'b+': 3.33, - 'b': 3, - 'b-': 2.67, - 'c+': 2.33, - 'c': 2, - 'c-': 1.67, - 'd+': 1.33, - 'd': 1, - 'f': 0, - 'p': 0, - 'n': 0} + grade_weights = { 'a': 4, 'a-': 3.69, + 'b+': 3.33, 'b': 3, 'b-': 2.67, + 'c+': 2.33, 'c': 2, 'c-': 1.67, + 'd+': 1.33, 'd': 1, 'f': 0, + 'p': 0, 'n': 0} # begin matching: studentFeatures = {} @@ -290,7 +292,7 @@ def StartMatch(request): ) } ) - #print(studentFeatures_per_course) + #print([i.student_grade for i in s.grade_set.all()]) studentFeatures.update({s.RIN: studentFeatures_per_course}) @@ -303,9 +305,7 @@ def StartMatch(request): #studentPrefs = {s: [c for c in r.sample(classes, r.randint(numClasses, numClasses ))] for s in students} - #print(studentPrefs) #studentFeatures = {s: {c: tuple(r.randint(0, 10) for i in range(numFeatures)) for c in classRank} for s, classRank in studentPrefs.items()} - #print(studentFeatures) classFeatures = {c: (r.randint(3, 10), r.randint(3, 10), r.randint(1, 10), r.randint(1, 10)) for c in classes} #classFeatures = {c: (0, 1000, 5, 5) for c in classes} @@ -317,27 +317,39 @@ def StartMatch(request): print("matching is stable\n") ''' + # create a context to store the results + result = Context() + result["courses"] = [] # list of courses + #print out some classes and students for (course, student_list) in classMatching.items(): print(course + ", cap: " + str(classCaps[course]) + ", features: ", classFeatures[course]) + this_course = Course.objects.filter(name = course).first() + + mentor_list = [] for s_rin in student_list: this_student = Mentor.objects.filter(RIN = s_rin).first() - this_course = Course.objects.filter(name = course).first() - query = Grade.objects.filter(student = this_student, course = this_course) - item = query.first() - print(" "+s_rin + " cumlative GPA: " + str(this_student.GPA).upper() + " grade: " + item.student_grade.upper() + ", has mentor exp: " + str(item.mentor_exp) ) - #print(" ",s , studentFeatures[s][course]) - print() + item = Grade.objects.filter(student = this_student, course = this_course).first() - unmatchedClasses = set(classes) - classMatching.keys() - unmatchedStudents = set(students) - matcher.studentMatching.keys() + print(" " + s_rin + " cumlative GPA: " + str(this_student.GPA).upper() + " grade: " + item.student_grade.upper() + ", has mentor exp: " + str(item.mentor_exp) ) - #unmatchedStudents uses the non-returned output matcher.studentMatching - #it's a dict from student to class they're in + #assign the course to this student + this_student.mentored_course = this_course + this_student.save() + new_mentor = {"name": this_student.first_name+" "+this_student.last_name, "GPA": this_student.GPA, "grade": item.student_grade.upper(), "Exp": str(item.mentor_exp)} + mentor_list.append(new_mentor) + + print() + result["courses"].append({"name": str(this_course), "mentors": mentor_list}) + + unmatchedClasses = set(classes) - classMatching.keys() + unmatchedStudents = set(students) - matcher.studentMatching.keys() print(f"{len(unmatchedClasses)} classes with no students") print(f"{len(unmatchedStudents)} students not in a class") - return render(request, 'mentors/index.html', {}) + + + return render(request, 'mentors/view_match_result.html', {'result': result}) # function to get preference order from a string # String orderStr @@ -359,9 +371,6 @@ def getPrefOrder(orderStr): else: final_order = json.loads(orderStr) - # the user hasn't ranked all the preferences yet - #if length != len(question.item_set.all()): - # return None - + return final_order From 976c131389ce9cada290828b693c3f5a54ca128e Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Fri, 15 Nov 2019 22:44:12 -0500 Subject: [PATCH 15/44] Add feature sliders that can be modified by the prof --- .../migrations/0003_course_mentor_cap.py | 18 +++++ compsocsite/mentors/models.py | 4 +- .../templates/mentors/course_feature.html | 67 +++++++++++++------ compsocsite/mentors/urls.py | 3 +- compsocsite/mentors/views.py | 45 ++++++++++--- 5 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 compsocsite/mentors/migrations/0003_course_mentor_cap.py diff --git a/compsocsite/mentors/migrations/0003_course_mentor_cap.py b/compsocsite/mentors/migrations/0003_course_mentor_cap.py new file mode 100644 index 00000000..99fa617d --- /dev/null +++ b/compsocsite/mentors/migrations/0003_course_mentor_cap.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-11-14 19:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0002_auto_20191113_2042'), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='mentor_cap', + field=models.IntegerField(default=0), + ), + ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index 2b06aa04..2b6567bf 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -193,7 +193,9 @@ class Course(models.Model): feature_course_GPA = models.IntegerField(default=0) feature_mentor_exp = models.IntegerField(default=0) - + # mentor capacity each course to have + mentor_cap = models.IntegerField(default = 0) + def __str__(self): return self.subject + " " + self.number + " " + self.name diff --git a/compsocsite/mentors/templates/mentors/course_feature.html b/compsocsite/mentors/templates/mentors/course_feature.html index 25395b7b..3dc8c749 100755 --- a/compsocsite/mentors/templates/mentors/course_feature.html +++ b/compsocsite/mentors/templates/mentors/course_feature.html @@ -2,12 +2,22 @@ {% load staticfiles %} {% block content %} + +
    -
    + {% csrf_token %} Choose Course: @@ -15,27 +25,44 @@ {% for course in courses %} {% endfor %} - + + +
    + {% if choosen_course %} +
    + {% csrf_token %} +
    {{choosen_course.name}}:
    + -
    -
      -
    • - Cumlative GPA :
      0
      -
      -
    • -
    • - Grade on this course before :
      0
      -
      -
    • -
    • - Have Mentor Experience :
      0
      -
      -
    • -
    -
    - -
    +
    Capacity: {{choosen_course.mentor_cap}}
    +
    +
      +
    • + Cumlative GPA :
      {{choosen_course.feature_cumlative_GPA}}
      +
      +
    • +
    • + Grade on this course before :
      {{choosen_course.feature_course_GPA}}
      +
      +
    • +
    • + Have taken the course :
      {{choosen_course.feature_has_taken}}
      +
      +
    • +
    • + Have Mentor Experience :
      {{choosen_course.feature_mentor_exp}}
      +
      +
    • +
    +
    + + + + + + + {% endif %}
    diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index 867fe849..c088b52e 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -23,7 +23,8 @@ url(r'^addStudentRandomfunc/$', views.addStudentRandom, name='addStudentRandomfunc'), url(r'^matchfunc/$', views.StartMatch, name='matchfunc'), - url(r'^searchcoursefunc/$', views.addcourse, name='searchcoursefunc'), + url(r'^searchcoursefunc/$', views.searchCourse, name='searchcoursefunc'), + url(r'^changefeaturefunc/$', views.changeFeature, name='changefeaturefunc'), url(r'^view_application$', login_required(views.view_applyView.as_view()), name='view_application'), url(r'^withdrawfunc/$', views.withdraw, name='withdrawfunc'), diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index c9e6d2e3..837db28d 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -19,10 +19,6 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from django.core import mail -from prefpy.mechanism import * -from prefpy.allocation_mechanism import * -from prefpy.gmm_mixpl import * -from prefpy.egmm_mixpl import * from django.conf import settings from django.template import Context @@ -217,11 +213,38 @@ def addcourse(request): subject = row[1], number = row[2], instructor = row[3], - ) + mentor_cap = r.randint(3, 10), + feature_cumlative_GPA = r.randint(3, 10), + feature_has_taken = r.randint(0, 10), + feature_course_GPA = r.randint(3, 10), + feature_mentor_exp = r.randint(0, 10), + ) print(row[0] + " " + row[1] + " " + row[2] + " successfully added.") return render(request, 'mentors/index.html', {}) +def searchCourse(request): + courses = Course.objects.all() + if request.method == 'POST': + + course_name = request.POST.get('courses', False) + choosen_course = Course.objects.filter(name = course_name).first() + return render(request, 'mentors/course_feature.html', {'courses': courses, 'choosen_course': choosen_course}) + +def changeFeature(request): + courses = Course.objects.all() + if request.method == 'POST': + + course_name = request.POST.get('course', False) + + choosen_course = Course.objects.filter(name = course_name).first() + choosen_course.feature_cumlative_GPA = request.POST.get('f1', False) + choosen_course.feature_course_GPA= request.POST.get('f2', False) + choosen_course.feature_has_taken = request.POST.get('f3', False) + choosen_course.feature_mentor_exp = request.POST.get('f4', False) + choosen_course.save() + return render(request, 'mentors/course_feature.html', {'courses': courses, 'choosen_course': choosen_course}) + # Randomly add students with assigned numbers def addStudentRandom(request): classes = [course.name for course in Course.objects.all()] @@ -297,25 +320,25 @@ def StartMatch(request): numFeatures = 4 # number of features we got - classes = [course.name for course in Course.objects.all()] - classCaps = {c: r.randint(3, 10) for c in classes} - students = [studnet.RIN for studnet in Mentor.objects.all()] + classes = [c.name for c in Course.objects.all()] + classCaps = {c.name: c.mentor_cap for c in Course.objects.all()} + students = [s.RIN for s in Mentor.objects.all()] numClasses = len(Course.objects.all()) studentPrefs = {s.RIN: [n.strip() for n in ast.literal_eval(s.course_pref[s.RIN])] for s in Mentor.objects.all()} #studentPrefs = {s: [c for c in r.sample(classes, r.randint(numClasses, numClasses ))] for s in students} #studentFeatures = {s: {c: tuple(r.randint(0, 10) for i in range(numFeatures)) for c in classRank} for s, classRank in studentPrefs.items()} - classFeatures = {c: (r.randint(3, 10), r.randint(3, 10), r.randint(1, 10), r.randint(1, 10)) for c in classes} + classFeatures = {c.name: (c.feature_cumlative_GPA, c.feature_course_GPA, c.feature_has_taken, c.feature_mentor_exp) for c in Course.objects.all()} #classFeatures = {c: (0, 1000, 5, 5) for c in classes} matcher = Matcher(studentPrefs, studentFeatures, classCaps, classFeatures) classMatching = matcher.match() - ''' + assert matcher.isStable() print("matching is stable\n") - ''' + # create a context to store the results result = Context() From c16058acfa598e664479a0cfb1e5f941bcc8e469 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 16 Nov 2019 16:07:07 -0500 Subject: [PATCH 16/44] Change the student application form to multipage --- compsocsite/mentors/forms.py | 207 +++++++++++++++++- .../migrations/0004_auto_20191116_1454.py | 18 ++ compsocsite/mentors/models.py | 4 +- .../templates/mentors/view_match_result.html | 4 +- compsocsite/mentors/urls.py | 5 + compsocsite/mentors/views.py | 175 +++++++++++++-- 6 files changed, 381 insertions(+), 32 deletions(-) create mode 100644 compsocsite/mentors/migrations/0004_auto_20191116_1454.py diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 24f63332..60486870 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -8,6 +8,193 @@ from crispy_forms.layout import * from crispy_forms.bootstrap import * +class MentorApplicationfoForm_step1(ModelForm): + class Meta: + model = Mentor + #fields = '__all__' + fields = ( 'RIN', + 'first_name', + 'last_name', + 'GPA', + 'email', + 'phone', + 'recommender', + ) + widgets = { + 'RIN': forms.TextInput(attrs={'placeholder': ' 661680100'}), + 'GPA': forms.TextInput(attrs={'placeholder': ' 3.6'}), + 'email': forms.TextInput(attrs={'placeholder': ' xxx@rpi.email'}), + 'phone': forms.TextInput(attrs={'placeholder': ' 5185941234'}), + } + def __init__(self, *args, **kwargs): + super(MentorApplicationfoForm_step1, self).__init__(*args, **kwargs) + courses = Course.objects.all() + course_layout = Div() + self.helper = FormHelper() + self.helper.layout = Layout( + HTML('== PERSONAL INFORMATION =='), + HTML("
    "), + Field('RIN', 'first_name', 'last_name', 'email', 'phone', 'GPA', css_class = ""), + HTML(""" +
    Please provide the name of someone in the CS Department who can recommend you. You are encouraged, but not required, to contact this person. +
    """), + HTML(""" +
    Please provide only a name here (describe additional circumstances in the freeform textbox below).
    + """), + Field('recommender', css_class = "inputline"), + HTML("
    "), + ) + self.helper.form_method = 'POST' + self.helper.label_class = "my_label" + self.helper.add_input(Submit('next', 'Next')) + +class MentorApplicationfoForm_step2(ModelForm): + class Meta: + model = Mentor + #fields = '__all__' + fields = ( 'compensation',) + def __init__(self, *args, **kwargs): + super(MentorApplicationfoForm_step2, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + HTML('== COMPENSATION AND RESPONSIBILITIES =='), + HTML("
    "), + HTML(""" +
    + We currently have funding for a fixed number of paid programming mentors. Therefore, if you apply for pay, you may be asked to work for credit instead. Course credit counts as a graded 1-credit or 2-credit free elective. In general, if you work an average of 1-3 hours per week, you will receive 1 credit; if you average 4 or more hours per week, you will receive 2 credits. Note that no more than 4 credits may be earned as an undergraduate (spanning all courses and all semesters). Please do not apply for credit if you have already earned 4 credits as a mentor; apply only for pay. +
    + """), + Field('compensation', css_class = ""), + HTML(""" +
    + For a paid position, you must follow + the given instructions to ensure you are paid; otherwise,  + another candidate will be selected.  More specifically, you  + will be required to have a student employment card. + This requires you to have a federal I-9 on file.  Bring appropriate  + identification with you to Amos Eaton 109 if this is your first  + time working for RPI; +
    + """), + HTML("
    "), + + ) + self.helper.form_method = 'POST' + self.helper.label_class = "my_label" + self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) + self.helper.add_input(Submit('next', 'Next')) + +class MentorApplicationfoForm_step3(forms.Form): + def __init__(self, *args, **kwargs): + super(MentorApplicationfoForm_step3, self).__init__(*args, **kwargs) + self.helper = FormHelper() + courses = Course.objects.all() + course_layout = Div() + for course in courses: + course_layout.append(HTML("
    ")) + course_layout.append( HTML("
    " + course.subject +" "+ course.number +" "+ course.name + "
    ") ) + grades = ( + ('a', 'A'), + ('a-', 'A-'), + ('b+', 'B+'), + ('b', 'B'), + ('b-', 'B-'), + ('c+', 'C+'), + ('c', 'C'), + ('c-', 'C-'), + ('d+', 'D+'), + ('d', 'D'), + ('f', 'F'), + ('p', 'Progressing'), + ('n', 'Not Taken'), + ) + choices_YN = ( + ('Y', 'YES'), + ('N', 'NO'), + ) + self.fields[course.name+ "_grade"] = forms.ChoiceField( + choices = grades, + label = "Grade on this course:", + ) + #self.initial[course.name+ "_grade"] = 'n' + + self.fields[course.name+ "_exp"] = forms.ChoiceField(choices = choices_YN, label = 'Have you mentored this class before? ') + #self.initial[course.name+ "_exp"] = 'N' + + course_layout.append(Field(course.name+ "_grade", label_class = "long_label")) + #course_layout.append(HTML('

    ')) + course_layout.append(InlineRadios(course.name+ "_exp")) + course_layout.append(HTML("
    ")) + + self.helper.layout = Layout( + HTML('== COURSE EXPERIENCE =='), + HTML("
    "), + course_layout, + HTML("
    "), + ) + self.helper.form_method = 'POST' + self.helper.label_class = "my_label" + self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) + self.helper.add_input(Submit('next', 'Next')) + + +class MentorApplicationfoForm_step4(forms.Form): + def __init__(self, *args, **kwargs): + super(MentorApplicationfoForm_step4, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.fields["pref_order"] = forms.CharField(max_length=10000) + self.fields["pref_order"].required = False + + self.helper.layout = Layout( + HTML('== COURSE PREFERENCE =='), + + HTML("
    "), + HTML('''Please rank the courses which you prefer to mentor, #1 means the highest prioity, and #2 means the second priority...etc. This will help us to allocate your position.'''), + + HTML(''' +
      +
      + {% for course in courses %} + {% if course %} +
        + +
        #{{ forloop.counter }}
        +
      • + {{ course.subject }} {{course.number}} +
      • +
      + {% endif %} + {% endfor %} +
    + '''), + # HTML part to store thr rankings + Field('pref_order', id='pref_order', css_class = 'pref_order', type = 'hidden'), + HTML("
    "), + ) + self.helper.form_method = 'POST' + self.helper.label_class = "my_label" + self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) + self.helper.add_input(Submit('next', 'Next', onclick="VoteUtil.submitPref();")) + + +class MentorApplicationfoForm_step5(forms.Form): + def __init__(self, *args, **kwargs): + super(MentorApplicationfoForm_step5, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + HTML('== SCHEDULING =='), + HTML("
    "), + HTML("Time slot plugin here"), + HTML("
    "), + ) + self.helper.form_method = 'POST' + self.helper.label_class = "my_label" + self.helper.add_input(Submit('submit', 'Submit')) + + +''' +# DEPRECATED class MentorApplicationfoForm(ModelForm): class Meta: model = Mentor @@ -21,7 +208,7 @@ class Meta: 'recommender', 'compensation', ) - ''' + help_texts = { 'RIN': _(' *Required'), 'first_name': _(' *Required'), @@ -31,7 +218,7 @@ class Meta: 'phone': _(' *Required'), 'recommender': _(' *Required'), } - ''' + widgets = { 'RIN': forms.TextInput(attrs={'placeholder': ' 661680100'}), 'GPA': forms.TextInput(attrs={'placeholder': ' 3.6'}), @@ -77,10 +264,10 @@ def __init__(self, *args, **kwargs): choices = grades, label = "Grade on this course:", ) - self.initial[course.name+ "_grade"] = 'n' + #self.initial[course.name+ "_grade"] = 'n' self.fields[course.name+ "_exp"] = forms.ChoiceField(choices = choices_YN, label = 'Have you mentored this class before? ') - self.initial[course.name+ "_exp"] = 'N' + #self.initial[course.name+ "_exp"] = 'N' course_layout.append(Field(course.name+ "_grade", label_class = "long_label")) #course_layout.append(HTML('

    ')) @@ -127,7 +314,6 @@ def __init__(self, *args, **kwargs):
    """), HTML("
    "), - ), AccordionGroup('== COURSE SELECTIONS ==', @@ -139,9 +325,9 @@ def __init__(self, *args, **kwargs): AccordionGroup('== COURSE RANKINGS ==', HTML("
    "), - HTML('''Please rank the courses which you prefer to mentor, #1 means the highest prioity, and #2 means the second priority...etc. This will help us to allocate your position.'''), + HTML(''Please rank the courses which you prefer to mentor, #1 means the highest prioity, and #2 means the second priority...etc. This will help us to allocate your position.''), - HTML('''
      + HTML(''
        {% for course in courses %} {% if course %} @@ -156,12 +342,12 @@ def __init__(self, *args, **kwargs): {% endif %} {% endfor %}
      - '''), - HTML(''' + ''), + HTML('' - '''), + ''), HTML("
    "), ), @@ -219,3 +405,4 @@ def save(self, *args, **kwargs): return new_applicant +''' \ No newline at end of file diff --git a/compsocsite/mentors/migrations/0004_auto_20191116_1454.py b/compsocsite/mentors/migrations/0004_auto_20191116_1454.py new file mode 100644 index 00000000..6b62ef3e --- /dev/null +++ b/compsocsite/mentors/migrations/0004_auto_20191116_1454.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-11-16 19:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0003_course_mentor_cap'), + ] + + operations = [ + migrations.AlterField( + model_name='mentor', + name='course_pref', + field=models.CharField(max_length=10000), + ), + ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index 2b6567bf..ea6ba0b3 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -224,8 +224,8 @@ class Mentor(models.Model): # Course preference of applicants, the data model here is dictionary # Yeah we can do charfield... will change it afterwards - course_pref = models.ForeignKey(Dict, on_delete = models.CASCADE, default = None) - + #course_pref = models.ForeignKey(Dict, on_delete = models.CASCADE, default = None) + course_pref = models.CharField(max_length=10000) # Many to one relation # mentor_course -> {s1, s2, s3, ...} # To get all the mentors in a course: course.mentor_set.all() diff --git a/compsocsite/mentors/templates/mentors/view_match_result.html b/compsocsite/mentors/templates/mentors/view_match_result.html index ff63e281..17fe8f9b 100755 --- a/compsocsite/mentors/templates/mentors/view_match_result.html +++ b/compsocsite/mentors/templates/mentors/view_match_result.html @@ -20,8 +20,8 @@
    - {% for course in result.courses %} -
    {{course.name}}
    + {% for course in result.courses|dictsort:"number"%} +
    {{course.name}}  [features: {{course.features}}]
    {% for mentor in course.mentors %}
      {{mentor.name}}, GPA: {{mentor.GPA}}, grade: {{mentor.grade}}, mentor experience: {{mentor.Exp}}
    {% endfor %} diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index c088b52e..f8b7068e 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -12,6 +12,11 @@ url(r'^apply$', login_required(views.ApplyView.as_view()), name='apply'), # personal info url(r'^apply/$', views.applystep, name='applyfunc1'), + url(r'^applystep2/$', views.applystep2, name='applystep2'), + url(r'^applystep3/$', views.applystep3, name='applystep3'), + url(r'^applystep4/$', views.applystep4, name='applystep4'), + url(r'^applystep5/$', views.applystep5, name='applystep5'), + url(r'^view-course$', login_required(views.CourseFeatureView.as_view()), name='view-course'), url(r'^view-course-result$', login_required(views.MatchResultView.as_view()), name='view-course-result'), diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 837db28d..43883ac0 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -150,36 +150,118 @@ def get_context_data(self, **kwargs): def get_queryset(self): return Course.objects.all() +def step1(request): + initial={'fn': request.session.get('fn', None)} + form = PersonForm(request.POST or None, initial=initial) + if request.method == 'POST': + if form.is_valid(): + request.session['fn'] = form.cleaned_data['fn'] + return HttpResponseRedirect(reverse('step2')) + return render(request, 'step1.html', {'form': form}) # apply step def applystep(request): + initial={'RIN': request.session.get('RIN', None), + 'first_name': request.session.get('first_name', None), + 'last_name': request.session.get('last_name', None), + 'GPA': request.session.get('GPA', None), + 'email': request.session.get('email', None), + 'phone': request.session.get('phone', None), + 'recommender': request.session.get('recommender', None)} + form = MentorApplicationfoForm_step1(request.POST or None, initial=initial) if request.method == 'POST': # initate a new mentor applicant - form = MentorApplicationfoForm(request.POST) if form.is_valid(): - new_applicant = form.save() - order_str = breakties(request.POST['pref_order']) - + #new_applicant = form.save() + #order_str = breakties(request.POST['pref_order']) + request.session['RIN'] = form.cleaned_data['RIN'] + request.session['first_name'] = form.cleaned_data['first_name'] + request.session['last_name'] = form.cleaned_data['last_name'] + request.session['GPA'] = form.cleaned_data['GPA'] + request.session['email'] = form.cleaned_data['email'] + request.session['phone'] = form.cleaned_data['phone'] + request.session['recommender'] = form.cleaned_data['recommender'] + ''' pref = new_applicant.course_pref pref[new_applicant.RIN] = order_str pref.save() l = pref[new_applicant.RIN] l = [n.strip() for n in ast.literal_eval(l)] # convert str list to actual list u['a', 'b', 'c'] -> ['a', 'b', 'c'] + ''' + return HttpResponseRedirect(reverse('mentors:applystep2')) + #return render(request, 'mentors/index.html', {'applied': True}) + + else: + print(form.errors) + + #return HttpResponseRedirect(reverse('groups:members', args=(group.id,))) + + return render(request, 'mentors/apply.html', {'apply_form': form}) + - return render(request, 'mentors/index.html', {'applied': True}) +# Compensation agreement +def applystep2(request): + initial={'compensation': request.session.get('compensation', None)} + form = MentorApplicationfoForm_step2(request.POST or None, initial=initial) + + if request.method == 'POST': + if form.is_valid(): + request.session['compensation'] = form.cleaned_data['compensation'] + return HttpResponseRedirect(reverse('mentors:applystep3')) else: print(form.errors) + return render(request, 'mentors/apply.html', {'apply_form': form}) - else: # display empty form - form = MentorApplicationfoForm() - #Mentor.applied = True - - #return HttpResponseRedirect(reverse('groups:members', args=(group.id,))) - courses = Course.objects.all() +# Course grade and mentor experience +def applystep3(request): + initial={} + for course in Course.objects.all(): + course_grade = course.name + "_grade" + course_exp = course.name + "_exp" + initial.update({course_grade: request.session.get(course_grade, 'n')}) + initial.update({course_exp: request.session.get(course_exp, 'N')}) + form = MentorApplicationfoForm_step3(request.POST or None, initial=initial) + + if request.method == 'POST': + if form.is_valid(): + for course in Course.objects.all(): + course_grade = course.name + "_grade" + course_exp = course.name + "_exp" + request.session[course_grade] = form.cleaned_data[course_grade] + request.session[course_exp] = form.cleaned_data[course_exp] + return HttpResponseRedirect(reverse('mentors:applystep4')) + else: + print(form.errors) + return render(request, 'mentors/apply.html', {'apply_form': form}) + + +# Course preference +def applystep4(request): + form = MentorApplicationfoForm_step4(request.POST or None, initial={}) + if request.method == 'POST': + if form.is_valid(): + request.session['pref_order'] = form.cleaned_data['pref_order'] + print(form.cleaned_data['pref_order']) + return HttpResponseRedirect(reverse('mentors:applystep5')) + else: + print(form.errors) + return render(request, 'mentors/apply.html', {'courses':Course.objects.all(), 'apply_form': form}) + + +# time slots +def applystep5(request): + form = MentorApplicationfoForm_step5(request.POST or None, initial={}) + if request.method == 'POST': + if form.is_valid(): + #order_str = breakties(request.POST['pref_order']) + submit_application(request) + return render(request, 'mentors/index.html',{}) + else: + print(form.errors) + return render(request, 'mentors/apply.html', {'courses':Course.objects.all(), 'apply_form': form}) - return render(request, 'mentors/apply.html', {'courses': courses, 'apply_form': form}) # return the prefer after brutally break ties @@ -193,6 +275,53 @@ def breakties(order_str): return l +def submit_application(request): + new_applicant = Mentor() + new_applicant.RIN = request.session["RIN"] + new_applicant.first_name = request.session["first_name"] + new_applicant.last_name = request.session["last_name"] + new_applicant.GPA = request.session["GPA"] + new_applicant.phone = request.session["phone"] + new_applicant.compensation = request.session["compensation"] + + # create a dictionary to store a list of preference + #rin = new_applicant.RIN + #pref = Dict() + #pref.name = rin + #pref[rin] = breakties(request.session["pref_order"]) + #pref.save() + + new_applicant.course_pref = breakties(request.session["pref_order"]) + new_applicant.save() + #orderStr = self.cleaned_data["pref_order"] + + # Save Grades on the course average + for course in Course.objects.all(): + course_grade = course.name + "_grade" + course_exp = course.name + "_exp" + + new_grade = Grade() + new_grade.student = new_applicant + new_grade.course = course + new_grade.student_grade = request.session[course_grade] # Grade on this course + new_grade.save() + + if (request.session[course_exp] == 'Y'): # Mentor Experience + new_grade.mentor_exp = True + else: + new_grade.mentor_exp = False + + # if the student has failed the class, or is progressing, we consider he did not take the course + if (new_grade.student_grade != 'p' and new_grade.student_grade != 'n' and new_grade.student_grade != 'f'): + new_grade.have_taken = True + else: + new_grade.have_taken = False + new_grade.save() + print(new_grade.course.name + ": " + new_grade.student_grade.upper()) + + return new_applicant + + # withdraw application, should add semester later def withdraw(request): if request.method == 'GET': @@ -223,14 +352,16 @@ def addcourse(request): return render(request, 'mentors/index.html', {}) +# Return the course searched def searchCourse(request): courses = Course.objects.all() if request.method == 'POST': - course_name = request.POST.get('courses', False) choosen_course = Course.objects.filter(name = course_name).first() return render(request, 'mentors/course_feature.html', {'courses': courses, 'choosen_course': choosen_course}) + +# Change the value of features of a selected course def changeFeature(request): courses = Course.objects.all() if request.method == 'POST': @@ -245,6 +376,8 @@ def changeFeature(request): choosen_course.save() return render(request, 'mentors/course_feature.html', {'courses': courses, 'choosen_course': choosen_course}) + + # Randomly add students with assigned numbers def addStudentRandom(request): classes = [course.name for course in Course.objects.all()] @@ -261,14 +394,17 @@ def addStudentRandom(request): new_applicant.last_name = str(i) new_applicant.GPA = round(random.uniform(2.0, 4)*100)/100 # simple round new_applicant.phone = 518596666 - + ''' pref = Dict() pref.name = new_applicant.RIN pref.save() new_applicant.course_pref = pref new_applicant.save() - new_applicant.course_pref[new_applicant.RIN] = r.sample(classes, r.randint(1, numClass)) + ''' + new_applicant.course_pref = r.sample(classes, r.randint(1, numClass)) + new_applicant.save() + for course in Course.objects.all(): new_grade = Grade(id=None) #glist = ['a','a-','b+','b','b-','c+','c','c-','d+','d','f','p','n'] @@ -324,7 +460,7 @@ def StartMatch(request): classCaps = {c.name: c.mentor_cap for c in Course.objects.all()} students = [s.RIN for s in Mentor.objects.all()] numClasses = len(Course.objects.all()) - studentPrefs = {s.RIN: [n.strip() for n in ast.literal_eval(s.course_pref[s.RIN])] for s in Mentor.objects.all()} + studentPrefs = {s.RIN: [n.strip() for n in ast.literal_eval(s.course_pref)] for s in Mentor.objects.all()} #studentPrefs = {s: [c for c in r.sample(classes, r.randint(numClasses, numClasses ))] for s in students} @@ -364,14 +500,17 @@ def StartMatch(request): mentor_list.append(new_mentor) print() - result["courses"].append({"name": str(this_course), "mentors": mentor_list}) + result["courses"].append({"name": str(this_course), + "number": str(this_course.number), + "features": classFeatures[course], + "mentors": mentor_list}) unmatchedClasses = set(classes) - classMatching.keys() unmatchedStudents = set(students) - matcher.studentMatching.keys() print(f"{len(unmatchedClasses)} classes with no students") print(f"{len(unmatchedStudents)} students not in a class") - + return render(request, 'mentors/view_match_result.html', {'result': result}) # function to get preference order from a string From 2e54887b83748bf37d157f0db54d19fd4ea9b238 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Tue, 19 Nov 2019 13:17:15 -0500 Subject: [PATCH 17/44] add new js for mentor system --- compsocsite/static/js/course.js | 1118 +++++++++++++++++++++++++++++++ 1 file changed, 1118 insertions(+) create mode 100644 compsocsite/static/js/course.js diff --git a/compsocsite/static/js/course.js b/compsocsite/static/js/course.js new file mode 100644 index 00000000..bc5e1f1c --- /dev/null +++ b/compsocsite/static/js/course.js @@ -0,0 +1,1118 @@ +// Helper JavaScript created for the voting page (detail.html) +var record = '[]'; //for recording two col behaviors +var temp_data; +var one_record = '{"one_column":[]}'; +var swit = ""; //for recording users' action on swritching between voting interfaces +var slider_record = '{"slider":[]}'; +var star_record = '{"star":[]}'; +var submissionURL = ""; +var order1 = ""; +var order2 = ""; +var flavor = ""; +var startTime = 0; +var allowTies = true; +var commentTime = ""; +var method = 2; // 1 is twoCol, 2 is oneCol, 3 is Slider +var methodIndicator = "two_column"; +var init_star = false; +var animOffset = 200; // animation speed of ranking UIs, 200 = 0.2s +var top_tier_layer = 0; + +function select(item){ + var d = (Date.now() - startTime).toString(); + temp_data = {"item":$(item).attr("id")}; + temp_data["time"] = [d]; + temp_data["rank"] = [dictYesNo()]; + if($(item).children()[0].checked){ + $(item).css('border-color', 'green'); + $(item).css('border-width', '5px'); + $(item).css('margin-top', '1px'); + $(item).css('margin-bottom', '1px'); + $($(item).children()[1]).removeClass('glyphicon-unchecked'); + $($(item).children()[1]).addClass('glyphicon-check'); + $($(item).children()[1]).css('color', "green"); + } + else{ + $(item).css('border-color', 'grey'); + $(item).css('border-width', '1px'); + $(item).css('margin-top', '5px'); + $(item).css('margin-bottom', '9px'); + $($(item).children()[1]).removeClass('glyphicon-check'); + $($(item).children()[1]).addClass('glyphicon-unchecked'); + $($(item).children()[1]).css('color', "grey"); + } + var temp = JSON.parse(record); + temp.push(temp_data); + record = JSON.stringify(temp); + //console.log(record); +} + +function select2(item){ + var d = (Date.now() - startTime).toString(); + temp_data = {"item":$(item).attr("id")}; + temp_data["time"] = [d]; + temp_data["rank"] = [dictYesNo2()]; + if($(item).children()[0].checked){ + $(item).css('border-color', 'green'); + $(item).css('border-width', '5px'); + $(item).css('margin-top', '1px'); + $(item).css('margin-bottom', '1px'); + $($(item).children()[1]).removeClass('glyphicon-unchecked'); + $($(item).children()[1]).addClass('glyphicon-check'); + $($(item).children()[1]).css('color', "green"); + } + else{ + $(item).css('border-color', 'grey'); + $(item).css('border-width', '1px'); + $(item).css('margin-top', '5px'); + $(item).css('margin-bottom', '9px'); + $($(item).children()[1]).removeClass('glyphicon-check'); + $($(item).children()[1]).addClass('glyphicon-unchecked'); + $($(item).children()[1]).css('color', "grey"); + } + var temp = JSON.parse(record); + temp.push(temp_data); + record = JSON.stringify(temp); + //console.log(record); +} + +//Get order of one or two column +function orderYesNo(num){ + if(num == 5){ list = '#yesNoList'; } + if(num == 6){ list = '#singleList'; } + var arr = $(list).children(); + var order = []; + var yes = []; + var no = []; + $.each(arr, function( index, value ){ + if(!(typeof $(value).children()[0] === "undefined")){ + if($(value).children()[0].checked){ yes.push($(value).attr("type")); } + else{ no.push($(value).attr("type")); } + } + }); + if(yes.length != 0){ order.push(yes); } + if(no.length != 0){ order.push(no); } + return order; +} + +// return the ranking based on vote +function orderCol(num){ + var arr; + if(num == 0){ arr = [$('#left-sortable')]; } + if(num == 1){ arr = [$('#left-sortable'), $('#right-sortable')]; } + else if(num == 2){ arr = [$('#left-sortable')]; } + var order = []; + $.each(arr, function( index, value ){ + value.children().each(function( index ){ + if( $( this ).children().size() > 0 ){ + var inner = []; + $( this ).children().each(function( index ){ + if(!$(this).hasClass("tier")) inner.push($( this ).attr('type')); + }); + order.push(inner); + } + }); + }); + + return order; +} + + +function orderSlideStar(str){ + var arr = []; + var values = []; + $('.' + str).each(function(i, obj){ + if(str == 'slide'){ + var score = $( this ).slider("option", "value"); + } + else if(str == 'star'){ + var score = parseFloat($( this ).rateYo("option", "rating")); + } + else{ return false; } + var type = $( this ).attr('type') + var bool = 0; + $.each(values, function( index, value ){ + if(value < score){ + values.splice(index, 0, score); + arr.splice(index, 0, [type]); + bool = 1; + return false; + }else if(value == score){ + arr[index].push(type); + bool = 1; + return false; + } + }); + if(bool == 0){ values.push(score); arr.push([type]); } + }); + return arr; +} + +function dictSlideStar(str){ + var arr = []; + var values = []; + var item_type = ".course-element"; + $('.' + str).each(function(i, obj){ + if(str == 'slide'){ + var score = $( this ).slider("option", "value"); + item_type = ".slider_item"; + } + else if(str == 'star'){ + var score = parseFloat($( this ).rateYo("option", "rating")); + item_type = ".star_item"; + } + else{ return false; } + var type = $( this ).attr('type'); + var bool = 0; + //console.log($(item_type + "[type='" + type + "']").attr('id')); + $.each(values, function( index, value ){ + if(value < score){ + var temp = {}; + temp["name"] = $(item_type + "[type='" + type + "']").attr('id'); + temp["score"] = score; + temp["ranked"] = 0; + values.splice(index, 0, score); + arr.splice(index, 0, [temp]); + bool = 1; + return false; + }else if(value == score){ + var temp = {}; + temp["name"] = $(item_type + "[type='" + type + "']").attr('id'); + temp["score"] = score; + temp["ranked"] = 0; + arr[index].push(temp); + bool = 1; + return false; + } + }); + if(bool == 0){ + var temp = {}; + temp["name"] = $(item_type + "[type='" + type + "']").attr('id'); + temp["score"] = score; + temp["ranked"] = 0; + values.push(score); + arr.push([temp]); + } + }); + var i; + for(i = 0; i < arr.length; i++){ + var j; + for(j = 0; j < arr[i].length; j++){ + arr[i][j]["tier"] = i+1; + } + } + return arr; +} + +function dictYesNo(){ + var arr = $('#yesNoList').children(); + var order = []; + var yes = []; + var no = []; + $.each(arr, function( index, value ){ + if(!(typeof $(value).children()[0] === "undefined")){ + temp = {}; + temp["name"] = $(value).attr("id"); + temp["ranked"] = 0; + if($(value).children()[0].checked){temp["tier"] = 1; yes.push(temp); } + else{temp["tier"] = 2; no.push(temp); } + } + }); + if(yes.length != 0){ order.push(yes); } + if(no.length != 0){ order.push(no); } + return order; +} + +function dictYesNo2(){ + var arr = $('.checkbox'); + var order = []; + var yes = []; + var no = []; + var i = 0; + $.each(arr, function( index, value ){ + if(!(typeof $(value).children()[0] === "undefined")){ + temp = {}; + temp["name"] = $(value).attr("id"); + temp["ranked"] = 0; + if(i == 0) { temp["position"] = "(1,1)";} + else if (i == 1){temp["position"] = "(1,2)";} + else if (i == 2){temp["position"] = "(2,1)";} + else{temp["position"] = "(2,2)";} + if($(value).children()[0].checked){temp["tier"] = 1; yes.push(temp); } + else{temp["tier"] = 2; no.push(temp); } + i++; + } + }); + if(yes.length != 0){ order.push(yes); } + if(no.length != 0){ order.push(no); } + return order; +} + +// User list +function dictCol(num){ + var arr; + if(num == 0){ arr = [$('#left-sortable')]; } + if(num == 1){ arr = [$('#left-sortable'), $('#right-sortable')]; } + else if(num == 2){ arr = [$('#one-sortable')]; } + var order = []; + var tier = 1; + var item_type = ".course-element"; + $.each(arr, function( index, value ){ + value.children().each(function( i1 ){ + if( $( this ).children().size() > 0 && $( this ).attr("class") != "top_tier"){ + var inner = []; + $( this ).children().each(function( i2 ){ + var temp = {}; + temp["name"] = $(item_type + "[type='" + $( this ).attr('type') + "']").attr('id'); + temp["utility"] = $(item_type + "[type='" + $( this ).attr('type') + "']").attr('title'); + temp["tier"] = tier; + temp["ranked"] = index; + inner.push(temp); + }); + order.push(inner); + tier++; + } + }); + }); + return order; +} + +function twoColSort( order ){ + var html = ""; + var tier = 1; + var emptyLine = "
    "; + html += emptyLine; + $.each(order, function(index, value){ + html += "
      #" + tier + "
      "; + $.each(value, function(i, v){ + html += "
    • "; + html += $(".course-element[type='" + v.toString() + "']").html(); + html += "
    • "; + }); + html += "
    "; + tier ++; + }); + html += emptyLine; + $('#left-sortable').html(html); + $('#right-sortable').html(""); + changeCSS(); + +} + +function oneColSort( order ){ +// var html = "
      "; + var html = ""; + var tier = 1; + var emptyLine = "
      "; + html += emptyLine; + $.each(order, function(index, value){ + html += "
        #" + tier + "
        "; + $.each(value, function(i, v){ + html += "
      • "; + html += $("#oneColSection .course-element[type='" + v.toString() + "']").html(); + html += "
      • "; + }); + html += "
      "; + tier ++; + + }); + //html += "
        "; + html += emptyLine; + $('#one-sortable').html(html); + changeCSS(); + +} + +function sliderSort( order ){ + $.each(order, function(index, value){ + $.each(value, function(i, v){ + $(".slide[type='" + v.toString() + "']").slider("value", Math.round(100 - (100 * index / order.length))); + $("#score" + $(".slide[type='" + v.toString() + "']").attr("id")).text(Math.round(100 - (100 * index / order.length))); + }); + }); +} + +function sliderZeroSort( order ){ + $.each(order, function(index, value){ + $.each(value, function(i, v){ + $(".slide[type='" + v.toString() + "']").slider("value", 0); + $("#score" + $(".slide[type='" + v.toString() + "']").attr("id")).text(0); + }); + }); +} + +function starSort( order ){ + init_star = true; + $.each(order, function(index, value){ + $.each(value, function(i, v){ + if(index >= 10){ $(".star[type='" + v.toString() + "']").rateYo("option", "rating", 0); } + else{ $(".star[type='" + v.toString() + "']").rateYo("option", "rating", Math.round(10 - (10 * index / Math.min(order.length, 10))) / 2); } + }); + }); + init_star = false; +} + +function yesNoSort( num, order ){ + $.each(order, function(index, value){ + $.each(value, function(i, v){ + var cb; + if(num == 5){ cb = ".checkbox[type='"; } + if(num == 6){ cb = ".checkbox_single[type='"; } + if(index == 0){ + $($(cb + v.toString() + "']").children()[0]).attr('checked', 'checked'); + $($(cb + v.toString() + "']").children()[1]).removeClass('glyphicon-unchecked'); + $($(cb + v.toString() + "']").children()[1]).addClass('glyphicon-check'); + $($(cb + v.toString() + "']").children()[1]).css('color', "green"); + $(cb + v.toString() + "']").css('border-color', 'green'); + $(cb + v.toString() + "']").css('border-width', '5px'); + $(cb + v.toString() + "']").css('margin-top', '1px'); + $(cb + v.toString() + "']").css('margin-bottom', '1px'); + } + else{ + $($(cb + v.toString() + "']").children()[0]).removeAttr('checked'); + $($(cb + v.toString() + "']").children()[1]).removeClass('glyphicon-check'); + $($(cb + v.toString() + "']").children()[1]).addClass('glyphicon-unchecked'); + $($(cb + v.toString() + "']").children()[1]).css('color', "grey"); + $(cb + v.toString() + "']").css('border-color', 'grey'); + $(cb + v.toString() + "']").css('border-width', '1px'); + $(cb + v.toString() + "']").css('margin-top', '5px'); + $(cb + v.toString() + "']").css('margin-bottom', '9px'); + } + }); + }); +} + +function yesNoZeroSort( order ){ + $.each(order, function(index, value){ + $.each(value, function(i, v){ + $($(".checkbox[type='" + v.toString() + "']").children()[0]).removeAttr('checked'); + $($(".checkbox[type='" + v.toString() + "']").children()[1]).removeClass('glyphicon-check'); + $($(".checkbox[type='" + v.toString() + "']").children()[1]).addClass('glyphicon-unchecked'); + $($(".checkbox[type='" + v.toString() + "']").children()[1]).css('color', "grey"); + $(".checkbox[type='" + v.toString() + "']").css('border-color', 'grey'); + $(".checkbox[type='" + v.toString() + "']").css('border-width', '1px'); + $(".checkbox[type='" + v.toString() + "']").css('margin-top', '5px'); + $(".checkbox[type='" + v.toString() + "']").css('margin-bottom', '9px'); + }); + }); +} + + +// change the behavior of the UI when the user change voting method +// or drop a new item +function changeCSS(){ + // if method is twocol + if(method == 1){ + $(".course_choice").css("width", "550px"); + $(".empty"). css("width", "550px"); + $(".col-placeHolder").css("width", "550px"); + + // extend the height of course_choice box if there exists more than 3 course-element in a row + // vice versa + $("#left-sortable").children(".course_choice").each(function(){ + size = $(this).children(":not(.ui-selected, .transporter)").size(); + //$(this).css("height", ((size-1)*40).toString() + "px"); + if(size > 4){ + size -= 1; + num = Math.ceil(size/3); + $(this).css("height", (num*40).toString() + "px"); + $(this).children(".tier").css( "height", (num*40).toString() + "px"); + $(this).children(".tier").css( "line-height", (num*40).toString() + "px"); + } + else{ + $(this).css("height", "40px"); + $(this).children(".tier").css( "height", "40px"); + $(this).children(".tier").css( "line-height", "40px"); + + } + }); + } + // if method is onecol, extend the selection bar + else if(method == 2){ + $(".course_choice").css("width", "800px"); + $(".empty"). css("width", "800px"); + $(".col-placeHolder").css("width", "800px"); + } + // extend the height of course_choice box if there exists more than 6 course-element in a row + // vice versa + $("#left-sortable").children(".course_choice").each(function(){ + size = $(this).children(":not(.ui-selected, .transporter)").size(); + if(size > 5){ + size -= 1; + num = Math.ceil(size/4); + $(this).css("height", (num*40).toString() + "px"); + $(this).children(".tier").css( "height", (num*40).toString() + "px"); + $(this).children(".tier").css( "line-height", (num*40).toString() + "px"); + } + else{ + $(this).css("height", "40px"); + $(this).children(".tier").css( "height", "40px"); + $(this).children(".tier").css( "line-height", "40px"); + } + }); + +} + +function changeMethod (value){ + var order; + var d = Date.now() - startTime; + if(method == 1){ + swit += d + ";1"; + order = orderCol(method); + + }else if(method == 2){ + swit += d + ";2"; + order = orderCol(method); + } + else if(method == 3){ + swit += d + ";3"; + order = orderSlideStar('slide'); + } + else if(method == 4){ + swit += d + ";4"; + order = orderSlideStar('star'); + } + else if(method == 5 || method == 6){ + order = orderYesNo(method); + } + method = value; + removeSelected(); + changeCSS(); + + if(method == 1){ swit += ";1;;"; methodIndicator = "two_column"; twoColSort(order); } + else if(method == 2){ swit += ";2;;"; methodIndicator = "one_column"; oneColSort(order); } + else if(method == 3){ swit += ";3;;"; methodIndicator = "slider"; sliderSort(order); } + else if(method == 4){ swit += ";4;;"; methodIndicator = "star"; init_star = true; starSort(order); init_star = false;} + else if(method == 5 || method == 6){ yesNoSort(method, order); } +}; + +function recordCommentTime(){ + if(commentTime == ""){ + var d = Date.now() - startTime ; + commentTime += d; + } + +} +// the VoteUtil object contains all the utility functions for the voting UI +var VoteUtil = (function () { + // returns true if the user is on a mobile device, else returns false + function isMobileAgent () { + return /Android|webOS|iPhone|iPod|greyBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); + } + + // clears all items from the left side and returns the right side to its default state + function clearAll() { + var d = (Date.now() - startTime).toString(); + temp_data = { + "item": "" + }; + temp_data["time"] = [d]; + temp_data["rank"] = [dictCol(1)]; + if (method == 1) { + var tier = $("#right-sortable").children().size()+1; + // move the left items over to the right side + $("#left-sortable").children().each(function(index) { + if ($(this).children().size() > 0) { + $(this).children().each(function(index) { + var temp = $("#right-sortable").html(); + //$(this).attr("onclick")="VoteUtil.moveToPref(this)"; + if (!$(this).hasClass("tier")) { + $("#right-sortable").html( + temp + "
          " + "
          #" + tier.toString() + "
          " + $(this)[0].outerHTML + "
        "); + tier++; + } + }); + } + }); + // clear the items from the left side + + $('#left-sortable').html(""); + + $(".course_choice").each(function(index) { + $(this).attr("onclick", "VoteUtil.moveToPref(this)"); + }); + + disableSubmission(); + // add the clear action to the record + //var d = Date.now() - startTime; + //record += d + "||"; + d = (Date.now() - startTime).toString(); + temp_data["time"].push(d); + temp_data["rank"].push(dictCol(1)); + var temp = JSON.parse(record); + temp.push(temp_data); + //temp["star"].push({"time":d, "action":"set", "value":rating.toString(), "item":$(this).parent().attr("id") }); + record = JSON.stringify(temp); + + } + } + + // submits the current left side preferences + function submitPref() { + var order; + var order_list; + var final_list; + var item_type = ".course-element"; + var record_data = {}; + $(".top_tier").remove(); + if(method == 1) {order_list = orderCol(0); final_list = dictCol(1);} + else if(method == 2){ order_list = orderCol(method); final_list = dictCol(2);} + else if(method == 3){ order_list = orderSlideStar('slide'); item_type = ".slider_item"; final_list = dictSlideStar('slide');} + else if(method == 4){ order_list = orderSlideStar('star'); item_type= ".star_item";final_list = dictSlideStar('star');} + else if(method == 5){ order_list = orderYesNo(method); item_type= ".checkbox"; final_list = dictYesNo();} + else if(method == 6){ order_list = orderYesNo(method); item_type= ".checkbox_single";} + else{location.reload(); } + var final_order = []; + for (var i = 0; i < order_list.length; i++) { + var sametier = []; + for (var j = 0; j < order_list[i].length; j++) { + sametier.push($(item_type + "[type='" + order_list[i][j].toString() + "']").attr('id')); + } + final_order.push(sametier); + } + order = JSON.stringify(final_order); + //var d = Date.now() - startTime; + //record += "S" + d; + var record_final = JSON.stringify(final_list); + var d = (Date.now() - startTime).toString(); + + record_data["data"] = JSON.parse(record); + record_data["submitted_ranking"] = final_list; + if(order1 != ""){ + record_data["initial_ranking"] = JSON.parse(order1); + } + else{ + record_data["initial_ranking"] = []; + } + record_data["time_submission"] = d; + record_data["platform"] = flavor; + var record_string = JSON.stringify(record_data); + $('.record_data').each(function(){ + $(this).val(record_string); + }); + + $('.pref_order').each(function(){ + $(this).val(order); + }); + + /* + $.ajax({ + url: submissionURL, + type: "POST", + data: {'data': record, 'csrfmiddlewaretoken': $('input[name="csrfmiddlewaretoken"]').val(), 'order1':order1,'final':record_final,'device':flavor,'commentTime':commentTime,'swit':swit,'submit_time':d,'ui':methodIndicator}, + success: function(){} + }); + */ + $('.submitbutton').css( "visibility","hidden"); + $('.submitting').css("visibility","visible"); + console.log(order_list); + + $('#pref_order').submit(); + }; + + // moves preference item obj from the right side to the bottom of the left side + function moveToPref(obj) { + $(obj).removeClass('choice2'); + $(obj).addClass('course_choice'); + var time = 100 + var prefcolumn = $('#left-sortable'); + var currentli = $(obj); + var tier = currentli.children().first().attr("alt"); + var tierRight = 1; + var tierleft = 1; + + var item = currentli.children().first().attr("id"); + var emptyLine = "
        "; + + var d = (Date.now() - startTime).toString(); + temp_data = { + "item": item + }; + temp_data["time"] = [d]; + temp_data["rank"] = [dictCol(1)]; + + if ($('#left-sortable').children().size() == 0) { + prefcolumn.append(emptyLine); + prefcolumn.append(currentli); + prefcolumn.append(emptyLine); + } else { + $('#left-sortable').children(".course_choice").last().after(currentli); + } + //record += d+ "::clickFrom::" + item + "::"+ tier+";;"; + + tier = currentli.children().first().attr("alt"); + if ($('#left-sortable').children().size() != 0) { + enableSubmission(); + } + $('#right-sortable').children(".choice2").each(function() { + $(this).children(".tier").text("#" + tierRight.toString()); + tierRight++; + }); + $('#left-sortable').children(".course_choice").each(function() { + $(this).children(".tier").text("#" +tierleft.toString()); + tierleft++; + }); + $('#left-sortable').children().each(function() { + $(this).removeAttr('onclick'); + }); + + d = (Date.now() - startTime).toString(); + temp_data["time"].push(d); + temp_data["rank"].push(dictCol(1)); + var temp = JSON.parse(record); + temp.push(temp_data); + record = JSON.stringify(temp); + //d = Date.now() - startTime; + //record += d+ "::clickTo::" + item + "::"+ tier+";;;"; + /* + if(methodIndicator == "two_column") + { + var d = (Date.now() - startTime).toString(); + var temp = JSON.parse(record); + temp["two_column"].push({"method":methodIndicator,"time":d, "action":"click", "from":prev_tier,"to": tier, "item":item }); + record = JSON.stringify(temp); + } + else + { + var d = (Date.now() - startTime).toString(); + var temp = JSON.parse(one_record); + temp["one_column"].push({"method":methodIndicator,"time":d, "action":"click", "from":prev_tier,"to": tier, "item":item }); + one_record = JSON.stringify(temp); + } + */ + }; + + // moves all items from the right side to the bottom of the left, preserving order + function moveAll() { + var d = (Date.now() - startTime).toString(); + temp_data = {"item":""}; + temp_data["time"] = [d]; + temp_data["rank"] = [dictCol(1)]; + $('.choice2').each(function(){ + $(this).removeClass('choice2'); + $(this).addClass('course_choice'); + }); + emptyLine = "
        "; + if ($('#right-sortable').children().size() > 0 && $('#left-sortable').children().size()== 0) + {$('#left-sortable' ).html( emptyLine + $( '#left-sortable' ).html() + $( '#right-sortable' ).html() + emptyLine); + } + else if ($('#right-sortable').children().size() > 0 && $('#left-sortable').children().size() > 0){ + $("#right-sortable").children(".course_choice").each(function(){ + moveToPref($(this)); + }); + } + $( '#right-sortable' ).html(""); + //VoteUtil.checkStyle(); + enableSubmission(); + $('.course_choice').each(function(){ + $(this).removeAttr('onclick'); + }); + //var d = Date.now() - startTime; + //record += d + ";;;"; + /* + if(methodIndicator == "two_column") + { + var d = (Date.now() - startTime).toString(); + var temp = JSON.parse(record); + temp["two_column"].push({"method":methodIndicator,"time":d, "action":"moveAll" }); + record = JSON.stringify(temp); + } + else + { + var d = (Date.now() - startTime).toString(); + var temp = JSON.parse(one_record); + temp["one_column"].push({"method":methodIndicator,"time":d, "action":"moveAll" }); + one_record = JSON.stringify(temp); + } + */ + d = (Date.now() - startTime).toString(); + temp_data["time"].push(d); + temp_data["rank"].push(dictCol(1)); + var temp = JSON.parse(record); + temp.push(temp_data); + record = JSON.stringify(temp); + }; + + // enables the submit button + function enableSubmission() { + if( VoteUtil.isMobileAgent() ){ + $(".submitbutton").css("display", "inline"); + }else{ + $(".submitbutton").prop("disabled",false); + } + } + + function disableSubmission(){ + if( VoteUtil.isMobileAgent() ){ + $(".submitbutton").css("display", "none"); + }else{ + $(".submitbutton").prop("disabled",true); + } + } + // returns the public members of the VoteUtil class + return { + isMobileAgent: isMobileAgent, + clearAll: clearAll, + submitPref: submitPref, + moveToPref: moveToPref, + moveAll: moveAll + } + +})() + +// === remove the ui-selected class for each choices === +function removeSelected(){ + $('.course-element').each(function() { + $(this).removeClass("ui-selected"); + }); +} + + + + +$( document ).ready(function() { + !function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); + + // Google Analytics + // ----------------------------------------------------------------------- + // (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + // (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + // m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + // })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + + // ga('create', 'UA-81006265-1', 'none'); + // //ga('create', 'UA-81006265-1', 'none','DetailTracker'); + // ga('send', 'pageview'); + // ga('send', 'event', 'Button', 'click', 'left-sortable'); + // //ga('DetailTracker.send', 'pageview'); + // ga(function(tracker) { + // // Logs the tracker created above to the console. + // console.log(tracker); + // }); + // var form=document.getElementById('left-sortable'); + // form.addEventListener('submit', function(event) { + + // // Prevents the browser from submiting the form + // // and thus unloading the current page. + // event.preventDefault(); + + // // Sends the event to Google Analytics and + // // resubmits the form once the hit is done. + // ga('send', 'event', 'Left Form', 'submit', { + // hitCallback: function() { + // form.submit(); + // } + // }); + // }); + // // ----------------------------------------------------------------------- + // // Google Tag Manager + // (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': + // new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], + // j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= + // '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); + // })(window,document,'script','dataLayer','GTM-59SLDM'); + // ----------------------------------------------------------------------- + + /* + var wholeHeight1 = $('#left-sortable')[0].scrollHeight; + var wholeHeight2 = $('#right-sortable')[0].scrollHeight; + if (wholeHeight1 > wholeHeight2) { + $('#right-sortable').css("height", wholeHeight1); + } else { + $('#left-sortable').css("height", wholeHeight2); + } + */ + // $('#left-sortable').sortable('refresh'); + // $('#right-sortable').sortable('refresh'); + + $('.hide1').mouseover(function(){ + $('.ept',this).show(); + }); + //VoteUtil.checkStyle(); + + function enableSubmission() { + $('.submitbutton').css("display", "inline"); + } + if($("#left-sortable").children(".course_choice").size() == 0) {$("#left-sortable").html("");} + // var type_num = 1, alt_num = 0; + // $('#one-sortable').children().each(function(index, value){ + // var string = ""; + // if($(value).children().length > 0){ console.log(alt_num); alt_num += 1; } + // $(value).children().each(function(i, v){ + // string += "
      • "; + // string += $(v).html(); + // string += "
      • \n"; + // type_num += 1; + // }); + // //console.log($(value).html()); + // //console.log(string); + // console.log("string " + string); + // $(value).html(string); + // console.log(value); + // }); + // $('#one-sortable').children().each(function(index, value){ + // //console.log(value); + // }); + + window.setInterval(function(){ + checkSubmission(); + setupSortable(); + }, 1000); + changeCSS(); + + function checkSubmission(){ + if($("#left-sortable").children().size() > 0){ + $(".submitbutton").prop("disabled", false); + } + else{ + $(".submitbutton").prop("disabled", true); + } + } + // reinitalize the sortable function + function setupSortable(){ + var str = ""; + if (allowTies){ + str = ".course_choice, .sortable-ties"; + $('.sortable-ties').selectable({ + cancel: '.tier', + filter: "li", + }); + } + else{ + str = ".sortable-ties"; + } + $('.sortable-ties').sortable({ + placeholder: "col-placeHolder", + handle: ".tier", + //items: "ul", + revert:'invalid', + start: function(e, ui){ + $(".col-placeHolder").hide(); + }, + change: function (e, ui){ + $(".col-placeHolder").hide().show(animOffset, function(){ + if (method == 1) { + $(".col-placeHolder").css("width", "550px"); + } + else if (method == 2) { + $(".col-placeHolder").css("width", "800px"); + } + $(".li-placeHolder").css({ "float": "none", "height": "40px", "margin": "0px 0px" }); + }); + changeCSS(); + }, + stop: function(e, ui) { + checkAll(); + removeSelected(); + resetEmpty(); + changeCSS(); + } + }); + + $('.course_choice').sortable({ + cancel: '.tier .empty', + cursorAt: { + top: 20, + left: 60 + }, + items: "li:not(.tier)", + placeholder: "li-placeHolder", + connectWith: str, + start: function(e, ui){ + //$(".li-placeHolder").hide(animOffset); // add animation for placeholder + }, + helper: function(e, item) { + if (!item.hasClass('ui-selected')) { + $('.ul').find('.ui-selected').removeClass('ui-selected'); + item.addClass('ui-selected'); + } + var selected = $('.ui-selected').clone(); + item.data('multidrag', selected); + $('.ui-selected').not(item).remove(); + + return $('
      • ').append(selected); + }, + receive: function (event, ui) { + changeCSS(); + }, + change: function(e, ui) { + // wait the animation to competer then change teh css of placeholder + $(".li-placeHolder").hide().show(animOffset, function(){ + if (ui.placeholder.parent().hasClass("sortable-ties")) { + if (method == 1) { + $(".li-placeHolder").css("width", "550px"); + } + else if (method == 2) { + $(".li-placeHolder").css("width", "800px"); + } + $(".li-placeHolder").css({ "float": "none", "height": "40px", "margin": "0px 0px" }); + } + else { + $(".li-placeHolder").css({ "float": "left", "width": "125px", "height": "35px", "margin": "2.5px 8px" }); + } + }); + changeCSS(); + }, + + stop: function(e, ui) { + var selected = ui.item.data('multidrag'); + selected.removeClass('ui-selected'); + ui.item.after(selected); + ui.item.remove(); + + checkAll(); + changeCSS(); + removeSelected(); + resetEmpty(); + }, + + + }).disableSelection(); + } + function checkAll() { + t1 = 1; + t2 = 1; + list = []; + html = "
          "; + if(method == 1){ html += "
          0
          "; } + if(method == 2){ html += "
          0
          "; } + + $('.sortable-ties').children().each(function() { + if ($(this).hasClass('course-element')) { + v = $(this).attr("type"); + //html += "
        • "; + //html += $(this).html(); + + html += "
        • "; + html += $(".course-element[type='" + v + "']").html(); + html += "
        • "; + // $(this).after(newi); + // $(this).remove(); + } + }); + html += "
        "; + $('.sortable-ties').children().each(function() { + if ($(this).hasClass('course-element')) { + $(this).after(html); + return false; //break + } + }); + $('.sortable-ties').children().each(function() { + if ($(this).hasClass('course-element')) { + $(this).remove(); + } + }); + $('.course_choice').each(function() { + if ($(this).children().size() == 1) + $(this).remove(); + }); + + $('.tier').each(function() { + $(this).text("\#"+t1.toString()); + t1++; + }); + + } + + // Reset the empty lines when a new item is placed + function resetEmpty(){ + $('.sortable-ties').children(".empty").each(function() { + $(this).remove(); + }); + var emptyLine = "
        "; + $('.sortable-ties').prepend(emptyLine); + $('.sortable-ties').append(emptyLine); // since .after() has a bug of not working in certain stances, we use .append() here + + } + //if the user updates existing preferences, the submit button should be shown + if ($('#right-sortable li').length == 0) { + enableSubmission(); + } + + $(".slide").each(function(){ + $(this).slider({ + step: 1, + range: 10, + max: 10, + slide: function( event, ui ) { + + $("#score" + this.id).text(ui.value); + }, + create: function(event, ui){ + var score = document.getElementById('score'+this.id).textContent; + $(this).slider('value', score); + }, + start: function (event, ui){ + var d = (Date.now() - startTime).toString(); + temp_data = {"item":$(this).parent().attr("id")}; + temp_data["time"] = [d]; + temp_data["rank"] = [dictSlideStar("slide")]; + + }, + stop: function (event, ui){ + var d = (Date.now() - startTime).toString(); + temp_data["time"].push(d); + temp_data["rank"].push(dictSlideStar("slide")); + var temp = JSON.parse(record); + temp.push(temp_data); + //temp["slider"].push({"time":d, "action":"stop", "value":ui.value.toString(), "item":$(this).parent().attr("id") }); + record = JSON.stringify(temp); + } + }); + }); + $(".star").each(function(){ + $(this).rateYo({ + numStars: 10, + fullStar: true, + onSet: function (rating, rateYoInstance) { + if(init_star == false) + { + var d = (Date.now() - startTime).toString(); + temp_data = {"item":$(this).parent().attr("id")}; + temp_data["time"] = [d]; + temp_data["rank"] = [dictSlideStar("star")]; + var temp = JSON.parse(record); + temp.push(temp_data); + //temp["star"].push({"time":d, "action":"set", "value":rating.toString(), "item":$(this).parent().attr("id") }); + record = JSON.stringify(temp); + } + } + }); + }); + var t = 1 + $("#twoColSection .course-element").each(function(){ + $(this).attr({type:t.toString()}); + t += 1; + }); + t = 1 + $("#oneColSection .course-element").each(function(){ + $(this).attr({type:t.toString()}); + t += 1; + }); +/* + if(deviceFlavor == "mobile" && firstTime){ + VoteUtil.moveAll(); + } + */ + var limit = 1; + $('.checkbox_single').on('change', function(evt) { + if($(this).children()[0].checked){ + $(this).siblings().each(function(){ + $(this).children()[0].checked = false; + select(this); + }); + }else{ + var ver = false; + $(this).siblings().each(function(){ + select(this); + if($(this).children()[0].checked == true){ + ver = true; + } + }); + if(!ver){ + $(this).children()[0].checked = true; + select(this); + } + } + }); +}); \ No newline at end of file From 3e462ce3d068f83a2a5b17d97ce0f5bf92b75ac4 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Tue, 19 Nov 2019 13:17:54 -0500 Subject: [PATCH 18/44] add new css for mentor system --- compsocsite/static/css/submission_form.css | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compsocsite/static/css/submission_form.css b/compsocsite/static/css/submission_form.css index 89e3c601..d25c4647 100755 --- a/compsocsite/static/css/submission_form.css +++ b/compsocsite/static/css/submission_form.css @@ -63,10 +63,14 @@ label.radio.inline{ .course-element.ui-selected { background-color: rgb(176, 197, 214) } -.inputline{ - padding: 10px 15px; - width:100%; +.ui-slider-handle{ + background-color: grey; +} + +.inputline{ + padding: 10px 15px; + width:100%; } .textline{ From 8024e845ede0b31138e4c2dd09f38bf175dab431 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Thu, 21 Nov 2019 10:38:56 -0500 Subject: [PATCH 19/44] Visual Changes & Time slots --- compsocsite/mentors/forms.py | 153 +++++++++++++----- .../migrations/0005_auto_20191120_2212.py | 23 +++ .../migrations/0006_auto_20191120_2236.py | 18 +++ compsocsite/mentors/models.py | 23 ++- .../mentors/templates/mentors/index.html | 13 +- compsocsite/mentors/urls.py | 3 +- compsocsite/mentors/views.py | 44 ++++- compsocsite/static/css/submission_form.css | 98 +++++------ 8 files changed, 281 insertions(+), 94 deletions(-) create mode 100644 compsocsite/mentors/migrations/0005_auto_20191120_2212.py create mode 100644 compsocsite/mentors/migrations/0006_auto_20191120_2236.py diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 60486870..5e56edb4 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -23,29 +23,41 @@ class Meta: widgets = { 'RIN': forms.TextInput(attrs={'placeholder': ' 661680100'}), 'GPA': forms.TextInput(attrs={'placeholder': ' 3.6'}), - 'email': forms.TextInput(attrs={'placeholder': ' xxx@rpi.email'}), + 'email': forms.TextInput(attrs={'readonly':'readonly', "style": "color:blue;"}), # Read-only email 'phone': forms.TextInput(attrs={'placeholder': ' 5185941234'}), } def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step1, self).__init__(*args, **kwargs) - courses = Course.objects.all() - course_layout = Div() self.helper = FormHelper() + #self.fields['email'] = user.email self.helper.layout = Layout( HTML('== PERSONAL INFORMATION =='), HTML("
        "), - Field('RIN', 'first_name', 'last_name', 'email', 'phone', 'GPA', css_class = ""), - HTML(""" -
        Please provide the name of someone in the CS Department who can recommend you. You are encouraged, but not required, to contact this person. -
        """), + Div('RIN', css_class = "inputline"), + HTML("""
        """), + Div('first_name', css_class = "inputline"), + HTML("""
        """), + Div('last_name', css_class = "inputline"), + HTML("""
        """), + + Div('email',css_class = "inputline"), + HTML("""
        """), + + Div('phone', css_class = "inputline"), + HTML("""
        """), + + Div('GPA', css_class = "inputline"), + HTML("""
        """), + HTML("""
        """), HTML(""" -
        Please provide only a name here (describe additional circumstances in the freeform textbox below).
        +
        Please provide the name of someone in the CS Department who can recommend you. You are encouraged, but not required, to contact this person. + Please provide only a name here (describe additional circumstances in the freeform textbox below).
        """), - Field('recommender', css_class = "inputline"), + Div('recommender', css_class = "inputline"), HTML("
        "), ) self.helper.form_method = 'POST' - self.helper.label_class = "my_label" + self.helper.label_class = "inputline" self.helper.add_input(Submit('next', 'Next')) class MentorApplicationfoForm_step2(ModelForm): @@ -59,23 +71,29 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( HTML('== COMPENSATION AND RESPONSIBILITIES =='), HTML("
        "), + HTML(""" -
        +
        We currently have funding for a fixed number of paid programming mentors. Therefore, if you apply for pay, you may be asked to work for credit instead. Course credit counts as a graded 1-credit or 2-credit free elective. In general, if you work an average of 1-3 hours per week, you will receive 1 credit; if you average 4 or more hours per week, you will receive 2 credits. Note that no more than 4 credits may be earned as an undergraduate (spanning all courses and all semesters). Please do not apply for credit if you have already earned 4 credits as a mentor; apply only for pay.
        """), - Field('compensation', css_class = ""), - HTML(""" -
        - For a paid position, you must follow - the given instructions to ensure you are paid; otherwise,  - another candidate will be selected.  More specifically, you  - will be required to have a student employment card. - This requires you to have a federal I-9 on file.  Bring appropriate  - identification with you to Amos Eaton 109 if this is your first  - time working for RPI; -
        - """), + HTML("""
        """), + Div('compensation', css_class = "inputline"), + HTML("""
        """), + HTML("""
        For a paid position, you must follow the given instructions to ensure you are paid; otherwise, + another candidate will be selected. More specifically, you will be required to have a student employment card. This requires + you to have a federal I-9 on file. Bring appropriate identification with you to Amos Eaton 125 if this is your first time working for RPI; + click here for a list of acceptable documents. + Note that original documents are required; copies cannot be accepted.
        """), + + HTML("""
        Detailed instructions are listed here: https://info.rpi.edu/student-employment/required-documents
        """), + + HTML("""
        Please note that to be paid, you will be required to submit your specific hours in SIS + every two weeks. This must be completed to get paid on time, and any late submissions will incur a fee charged to the department.
        """), + + HTML("""
        If you are unable to attend your assigned labs or office hours (e.g., illness, interview, conference, etc.), you must + notify your graduate lab TA and instructor and help arrange a substitute mentor. Unexcused absences will cause you to either earn a lower letter grade or not be asked to mentor again.
        """), HTML("
        "), ) @@ -89,10 +107,10 @@ def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step3, self).__init__(*args, **kwargs) self.helper = FormHelper() courses = Course.objects.all() - course_layout = Div() + course_layout = Field() for course in courses: - course_layout.append(HTML("
        ")) - course_layout.append( HTML("
        " + course.subject +" "+ course.number +" "+ course.name + "
        ") ) + course_layout.append( HTML('''
        ''' + str(course)+ "
        ") ) + course_layout.append(HTML("       ")) grades = ( ('a', 'A'), ('a-', 'A-'), @@ -118,13 +136,15 @@ def __init__(self, *args, **kwargs): ) #self.initial[course.name+ "_grade"] = 'n' - self.fields[course.name+ "_exp"] = forms.ChoiceField(choices = choices_YN, label = 'Have you mentored this class before? ') + self.fields[course.name+ "_exp"] = forms.ChoiceField(choices = choices_YN, label = "") #self.initial[course.name+ "_exp"] = 'N' + course_layout.append(HTML("""""")) + course_layout.append(Field(course.name+ "_grade", label_class="float: left;")) + #course_layout.append(HTML("""
        """)) + course_layout.append(HTML("""""")) + course_layout.append(Field(course.name+ "_exp")) + course_layout.append(HTML("""
        """)), - course_layout.append(Field(course.name+ "_grade", label_class = "long_label")) - #course_layout.append(HTML('

        ')) - course_layout.append(InlineRadios(course.name+ "_exp")) - course_layout.append(HTML("
        ")) self.helper.layout = Layout( HTML('== COURSE EXPERIENCE =='), @@ -133,7 +153,7 @@ def __init__(self, *args, **kwargs): HTML(""), ) self.helper.form_method = 'POST' - self.helper.label_class = "my_label" + self.helper.form_show_labels = False self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) self.helper.add_input(Submit('next', 'Next')) @@ -149,7 +169,8 @@ def __init__(self, *args, **kwargs): HTML('== COURSE PREFERENCE =='), HTML("
        "), - HTML('''Please rank the courses which you prefer to mentor, #1 means the highest prioity, and #2 means the second priority...etc. This will help us to allocate your position.'''), + HTML('''
        Please rank the courses which you prefer to mentor, #1 means the highest prioity, and #2 means the second priority...etc. The rankings will help us to allocate your position.
        '''), + HTML("""
        """), HTML('''
          @@ -173,25 +194,81 @@ def __init__(self, *args, **kwargs): HTML("
        "), ) self.helper.form_method = 'POST' - self.helper.label_class = "my_label" self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) self.helper.add_input(Submit('next', 'Next', onclick="VoteUtil.submitPref();")) - +# Time slots availble for students class MentorApplicationfoForm_step5(forms.Form): + + # widgets = { 'other_times': forms.Textarea(attrs={'cols': 80, 'rows': 40})} + def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step5, self).__init__(*args, **kwargs) self.helper = FormHelper() + time_slots_choices = ( + ('M_4:00-4:50PM', 'M 4:00-4:50PM'), + ('M_4:00-5:50PM', 'M 4:00-5:50PM'), + ('M_5:00-5:50PM', 'M 5:00-5:50PM'), + ('M_6:00-6:50PM', 'M 6:00-6:50PM'), + ('T_10:00-11:50AM', 'T 10:00-11:50AM'), + ('T_12:00-1:50PM', 'T 12:00-1:50PM'), + ('T_2:00-3:50PM', 'T 2:00-3:50PM'), + ('T_4:00-4:50PM', 'T 4:00-4:50PM'), + ('T_5:00-5:50AM', 'T 5:00-5:50AM'), + ('T_6:00-6:50PM', 'T 6:00-6:50PM'), + ('W_10:00-11:50AM', 'W 10:00-11:50AM'), + ('W_12:00-1:50PM', 'W 12:00-1:50PM'), + ('W_2:00-3:50PM', 'W 2:00-3:50PM'), + ('W_4:00-4:50PM', 'W 4:00-4:50PM'), + ('W_5:00-5:50AM', 'W 5:00-5:50AM'), + ('W_6:00-6:50PM', ' W 6:00-7:50PM'), + ('T_4:00-5:50AM', 'T 4:00-5:50AM'), + ('T_6:00-6:50PM', 'T 6:00-7:50PM'), + ) + self.fields["time_slots"] = forms.MultipleChoiceField( + choices=time_slots_choices, + label=False, + required=False, + widget=forms.CheckboxSelectMultiple() + ) + + self.fields["time_slots"].label = False + self.fields["other_times"] = forms.CharField(widget=forms.Textarea(attrs={'cols': 100, 'rows': 8})) + self.helper.layout = Layout( HTML('== SCHEDULING =='), HTML("
        "), - HTML("Time slot plugin here"), + HTML('''
        Indicate your availability for the + Spring 2020 semester by carefully checking all boxes that apply. The more boxes you check, + the more likely you will be a mentor. Note that there is overlap in some of the days/times listed below.
        '''), + Div("time_slots"), + HTML("""
        """), + HTML('''
        Please list other days/times of additional availability. The more days/times you are + available, the more likely you will be selected as a mentor. Do not leave this blank.
        '''), + Field('other_times', style = "width:100%"), HTML("
        "), ) self.helper.form_method = 'POST' - self.helper.label_class = "my_label" - self.helper.add_input(Submit('submit', 'Submit')) + self.helper.form_show_labels = False + self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) + self.helper.add_input(Submit('next', 'Next', onclick="VoteUtil.submitPref();")) + +class MentorApplicationfoForm_step6(forms.Form): + def __init__(self, *args, **kwargs): + super(MentorApplicationfoForm_step6, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.fields["relevant_info"] = forms.CharField(widget=forms.Textarea(attrs={'cols': 100, 'rows': 8})) + self.helper.layout = Layout( + HTML('== ADDITIONAL INFOMATION =='), + HTML("
        "), + HTML('''
        Please provide any other relevant information about yourself in the space below, including your mentoring preferences (i.e., which courses you'd prefer to mentor, which courses you'd prefer not to mentor, other extracurricular CS activities you're involved with, etc.). Do not leave this blank.
        '''), + Field('relevant_info', style = "width:100%"), + HTML("
        "), + ) + self.helper.form_method = 'POST' + self.helper.form_show_labels = False + self.helper.add_input(Submit('submit', 'Submit')) ''' # DEPRECATED diff --git a/compsocsite/mentors/migrations/0005_auto_20191120_2212.py b/compsocsite/mentors/migrations/0005_auto_20191120_2212.py new file mode 100644 index 00000000..89021094 --- /dev/null +++ b/compsocsite/mentors/migrations/0005_auto_20191120_2212.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.1 on 2019-11-21 03:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0004_auto_20191116_1454'), + ] + + operations = [ + migrations.AddField( + model_name='mentor', + name='time_slots', + field=models.CharField(choices=[('M_4:00-4:50PM', 'M 4:00-4:50PM'), ('M_4:00-5:50PM', 'M 4:00-5:50PM'), ('M_5:00-5:50PM', 'M 5:00-5:50PM'), ('M_6:00-6:50PM', 'M 6:00-6:50PM'), ('T_10:00-11:50AM', 'T 10:00-11:50AM'), ('T_12:00-1:50PM', 'T 12:00-1:50PM'), ('T_2:00-3:50PM', 'T 2:00-3:50PM'), ('T_4:00-4:50PM', 'T 4:00-4:50PM'), ('T_5:00-5:50AM', 'T 5:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-6:50PM'), ('W_10:00-11:50AM', 'W 10:00-11:50AM'), ('W_12:00-1:50PM', 'W 12:00-1:50PM'), ('W_2:00-3:50PM', 'W 2:00-3:50PM'), ('W_4:00-4:50PM', 'W 4:00-4:50PM'), ('W_5:00-5:50AM', 'W 5:00-5:50AM'), ('W_6:00-6:50PM', ' W 6:00-7:50PM'), ('T_4:00-5:50AM', 'T 4:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-7:50PM')], default=None, max_length=18), + ), + migrations.AlterField( + model_name='mentor', + name='compensation', + field=models.CharField(choices=[('1', 'Pay ($14/hour) '), ('2', 'Credit'), ('3', 'No Preference')], default='1', max_length=1), + ), + ] diff --git a/compsocsite/mentors/migrations/0006_auto_20191120_2236.py b/compsocsite/mentors/migrations/0006_auto_20191120_2236.py new file mode 100644 index 00000000..484505a5 --- /dev/null +++ b/compsocsite/mentors/migrations/0006_auto_20191120_2236.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-11-21 03:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0005_auto_20191120_2212'), + ] + + operations = [ + migrations.AlterField( + model_name='mentor', + name='time_slots', + field=models.CharField(choices=[('M_4:00-4:50PM', 'M 4:00-4:50PM'), ('M_4:00-5:50PM', 'M 4:00-5:50PM'), ('M_5:00-5:50PM', 'M 5:00-5:50PM'), ('M_6:00-6:50PM', 'M 6:00-6:50PM'), ('T_10:00-11:50AM', 'T 10:00-11:50AM'), ('T_12:00-1:50PM', 'T 12:00-1:50PM'), ('T_2:00-3:50PM', 'T 2:00-3:50PM'), ('T_4:00-4:50PM', 'T 4:00-4:50PM'), ('T_5:00-5:50AM', 'T 5:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-6:50PM'), ('W_10:00-11:50AM', 'W 10:00-11:50AM'), ('W_12:00-1:50PM', 'W 12:00-1:50PM'), ('W_2:00-3:50PM', 'W 2:00-3:50PM'), ('W_4:00-4:50PM', 'W 4:00-4:50PM'), ('W_5:00-5:50AM', 'W 5:00-5:50AM'), ('W_6:00-6:50PM', ' W 6:00-7:50PM'), ('T_4:00-5:50AM', 'T 4:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-7:50PM')], default='', max_length=18), + ), + ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index ea6ba0b3..a2bce7c7 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -216,11 +216,32 @@ class Mentor(models.Model): recommender = models.CharField(max_length=50) # Compensation Choices compensation_choice = ( - ('1', 'Pay'), + ('1', 'Pay ($14/hour) '), ('2', 'Credit'), ('3', 'No Preference'), ) compensation = models.CharField(max_length=1, choices = compensation_choice, default='1') + time_slots_choices = ( + ('M_4:00-4:50PM', 'M 4:00-4:50PM'), + ('M_4:00-5:50PM', 'M 4:00-5:50PM'), + ('M_5:00-5:50PM', 'M 5:00-5:50PM'), + ('M_6:00-6:50PM', 'M 6:00-6:50PM'), + ('T_10:00-11:50AM', 'T 10:00-11:50AM'), + ('T_12:00-1:50PM', 'T 12:00-1:50PM'), + ('T_2:00-3:50PM', 'T 2:00-3:50PM'), + ('T_4:00-4:50PM', 'T 4:00-4:50PM'), + ('T_5:00-5:50AM', 'T 5:00-5:50AM'), + ('T_6:00-6:50PM', 'T 6:00-6:50PM'), + ('W_10:00-11:50AM', 'W 10:00-11:50AM'), + ('W_12:00-1:50PM', 'W 12:00-1:50PM'), + ('W_2:00-3:50PM', 'W 2:00-3:50PM'), + ('W_4:00-4:50PM', 'W 4:00-4:50PM'), + ('W_5:00-5:50AM', 'W 5:00-5:50AM'), + ('W_6:00-6:50PM', ' W 6:00-7:50PM'), + ('T_4:00-5:50AM', 'T 4:00-5:50AM'), + ('T_6:00-6:50PM', 'T 6:00-7:50PM'), + ) + time_slots = models.CharField(choices = time_slots_choices, max_length = len(time_slots_choices), default= "") # Course preference of applicants, the data model here is dictionary # Yeah we can do charfield... will change it afterwards diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index c178561e..5b0ff27a 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -61,25 +61,32 @@
        +
        + Admin +
        {% csrf_token %} -

        Add Course (DEV)

        + Add Course (DEV):
        {% csrf_token %} -

        Add Student Random (DEV)

        + Add Student Random (DEV):
        {% csrf_token %} -

        Match

        + Match:
        +
        + {% csrf_token %} + +
        diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index f8b7068e..b3f432b3 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -16,9 +16,10 @@ url(r'^applystep3/$', views.applystep3, name='applystep3'), url(r'^applystep4/$', views.applystep4, name='applystep4'), url(r'^applystep5/$', views.applystep5, name='applystep5'), + url(r'^applystep6/$', views.applystep6, name='applystep6'), url(r'^view-course$', login_required(views.CourseFeatureView.as_view()), name='view-course'), - url(r'^view-course-result$', login_required(views.MatchResultView.as_view()), name='view-course-result'), + url(r'^view-course-result$', login_required(views.viewResultPage), name='view-course-result'), # compensation and responsbility #url(r'^applyfunc2/$', views.applystep, name='applyfunc2'), diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 43883ac0..2190de2c 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -165,7 +165,7 @@ def applystep(request): 'first_name': request.session.get('first_name', None), 'last_name': request.session.get('last_name', None), 'GPA': request.session.get('GPA', None), - 'email': request.session.get('email', None), + 'email': request.user.email, 'phone': request.session.get('phone', None), 'recommender': request.session.get('recommender', None)} form = MentorApplicationfoForm_step1(request.POST or None, initial=initial) @@ -178,7 +178,7 @@ def applystep(request): request.session['first_name'] = form.cleaned_data['first_name'] request.session['last_name'] = form.cleaned_data['last_name'] request.session['GPA'] = form.cleaned_data['GPA'] - request.session['email'] = form.cleaned_data['email'] + request.session['email'] = request.user.email, request.session['phone'] = form.cleaned_data['phone'] request.session['recommender'] = form.cleaned_data['recommender'] ''' @@ -189,6 +189,7 @@ def applystep(request): l = pref[new_applicant.RIN] l = [n.strip() for n in ast.literal_eval(l)] # convert str list to actual list u['a', 'b', 'c'] -> ['a', 'b', 'c'] ''' + print('yes') return HttpResponseRedirect(reverse('mentors:applystep2')) #return render(request, 'mentors/index.html', {'applied': True}) @@ -250,18 +251,29 @@ def applystep4(request): return render(request, 'mentors/apply.html', {'courses':Course.objects.all(), 'apply_form': form}) -# time slots +# Time slots page def applystep5(request): form = MentorApplicationfoForm_step5(request.POST or None, initial={}) if request.method == 'POST': if form.is_valid(): #order_str = breakties(request.POST['pref_order']) submit_application(request) - return render(request, 'mentors/index.html',{}) + return HttpResponseRedirect(reverse('mentors:applystep6')) else: print(form.errors) return render(request, 'mentors/apply.html', {'courses':Course.objects.all(), 'apply_form': form}) +# Students Additional Page +def applystep6(request): + form = MentorApplicationfoForm_step6(request.POST or None, initial={}) + if request.method == 'POST': + if form.is_valid(): + #order_str = breakties(request.POST['pref_order']) + submit_application(request) + return render(request, 'mentors/index.html',{}) + else: + print(form.errors) + return render(request, 'mentors/apply.html', {'courses':Course.objects.all(), 'apply_form': form}) # return the prefer after brutally break ties @@ -511,7 +523,28 @@ def StartMatch(request): print(f"{len(unmatchedStudents)} students not in a class") - return render(request, 'mentors/view_match_result.html', {'result': result}) + return render(request, 'mentors/view_match_result.html', {'result': viewMatchResult()}) + +def viewResultPage(request): + return render(request, 'mentors/view_match_result.html', {'result': viewMatchResult()}) + +def viewMatchResult(): + # create a context to store the results + result = Context() + result["courses"] = [] # list of courses + for course in Course.objects.all(): + mentor_list = [] + for student in course.mentor_set.all(): + item = Grade.objects.filter(student = student, course = course).first() + new_mentor = {"name": student.first_name+" "+student.last_name, "GPA": student.GPA, "grade": item.student_grade.upper(), "Exp": str(item.mentor_exp)} + mentor_list.append(new_mentor) + + result["courses"].append({"name": str(course), + "number": str(course.number), + "features": (course.feature_cumlative_GPA, course.feature_course_GPA, course.feature_has_taken, course.feature_mentor_exp) , + "mentors": mentor_list}) + return result + # function to get preference order from a string # String orderStr @@ -536,3 +569,4 @@ def getPrefOrder(orderStr): return final_order + diff --git a/compsocsite/static/css/submission_form.css b/compsocsite/static/css/submission_form.css index d25c4647..63c84835 100755 --- a/compsocsite/static/css/submission_form.css +++ b/compsocsite/static/css/submission_form.css @@ -11,23 +11,28 @@ width: 33%; display: inline-block; } + label.radio.inline{ - width: 33%; + float: left; padding: 0px 15px; } + .course_title{ font: 80px; color: red; font-weight: bold; + float: left; } -.control-group{ - width:100%; -} + .course_block{ width:100%; height:200px; } +.label_special{ + float:left; + color:red; +} .course_choice { white-space: nowrap; background:rgb(223, 222, 222); @@ -69,60 +74,61 @@ label.radio.inline{ } .inputline{ - padding: 10px 15px; - width:100%; -} + margin: 2px 8px 4px 8px; + display: block; -.textline{ - padding: 10px 15px; +} +.textline { + margin: 2px 8px 12px 8px; + display: block; } -.vspacewithline{ - padding: 10px 15px; - - width:100%; - height: 1px; +.controls{ + float: left; } -* {box-sizing: border-box} -/* Style the tab */ -.tab { +.control-group{ + display: block; float: left; - border: 1px solid #ccc; - background-color: #f1f1f1; - width: 30%; - height: 300px; } -/* Style the buttons that are used to open the tab content */ -.tab button { +.control-label{ + clear: both; display: block; - background-color: inherit; - color: black; - padding: 22px 16px; - width: 100%; - border: none; - outline: none; - text-align: left; - cursor: pointer; - transition: 0.3s; + width: 128px; + float: left; + text-align: right; + padding-right: 8px; + white-space: nowrap; } -/* Change background color of buttons on hover */ -.tab button:hover { - background-color: #ddd; +.textinput { } -/* Create an active/current "tab button" class */ -.tab button.active { - background-color: #ccc; +.checkbox{ + width:20%; + height:30px; + display: inline-block; + position: relative; + padding-left: 35px; } -/* Style the tab content */ -.tabcontent { - float: left; - padding: 0px 12px; - border: 1px solid #ccc; - width: 70%; - border-left: none; -} \ No newline at end of file +.emptyspace { + clear: both; + +} + +.vspace { + clear: both; + margin-top: 6px; + padding-bottom: 6px; +} + +.vspacewithline { + clear: both; + margin-top: 12px; + padding-bottom: 11px; + border-top: 1px solid #d5dfe5; +} + + From 76c0cf34138190cb784527afe28be4710c86fd42 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Thu, 21 Nov 2019 12:25:36 -0500 Subject: [PATCH 20/44] changes --- compsocsite/mentors/forms.py | 36 ++++++++++++++----- .../migrations/0007_auto_20191121_1153.py | 28 +++++++++++++++ compsocsite/mentors/models.py | 9 ++++- .../mentors/templates/mentors/apply.html | 3 +- .../mentors/templates/mentors/index.html | 35 ++++++++++++++++-- compsocsite/mentors/views.py | 23 ++++++++++-- 6 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 compsocsite/mentors/migrations/0007_auto_20191121_1153.py diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 5e56edb4..7fcc3ae2 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -198,13 +198,19 @@ def __init__(self, *args, **kwargs): self.helper.add_input(Submit('next', 'Next', onclick="VoteUtil.submitPref();")) # Time slots availble for students -class MentorApplicationfoForm_step5(forms.Form): - - # widgets = { 'other_times': forms.Textarea(attrs={'cols': 80, 'rows': 40})} +class MentorApplicationfoForm_step5(ModelForm): + class Meta: + model = Mentor + fields = ( 'other_times',) + widgets = { + #'time_slots': forms.CheckboxSelectMultiple(), + 'other_times': forms.Textarea(attrs={'cols': 100, 'rows': 8}) + } def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step5, self).__init__(*args, **kwargs) self.helper = FormHelper() + time_slots_choices = ( ('M_4:00-4:50PM', 'M 4:00-4:50PM'), ('M_4:00-5:50PM', 'M 4:00-5:50PM'), @@ -231,10 +237,8 @@ def __init__(self, *args, **kwargs): required=False, widget=forms.CheckboxSelectMultiple() ) - - self.fields["time_slots"].label = False - self.fields["other_times"] = forms.CharField(widget=forms.Textarea(attrs={'cols': 100, 'rows': 8})) - + + self.helper.layout = Layout( HTML('== SCHEDULING =='), HTML("
        "), @@ -253,7 +257,16 @@ def __init__(self, *args, **kwargs): self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) self.helper.add_input(Submit('next', 'Next', onclick="VoteUtil.submitPref();")) -class MentorApplicationfoForm_step6(forms.Form): + + +class MentorApplicationfoForm_step6(ModelForm): + class Meta: + model = Mentor + fields = ( 'relevant_info', ) + widgets = { + 'relevant_info': forms.Textarea(attrs={'cols': 100, 'rows': 8}) + } + def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step6, self).__init__(*args, **kwargs) self.helper = FormHelper() @@ -264,11 +277,16 @@ def __init__(self, *args, **kwargs): HTML("
        "), HTML('''
        Please provide any other relevant information about yourself in the space below, including your mentoring preferences (i.e., which courses you'd prefer to mentor, which courses you'd prefer not to mentor, other extracurricular CS activities you're involved with, etc.). Do not leave this blank.
        '''), Field('relevant_info', style = "width:100%"), + HTML("""
        """), + + HTML('''
        After you choose to submit, you can save your profile and change it if needed at any time after.
        '''), + HTML("
        "), ) self.helper.form_method = 'POST' self.helper.form_show_labels = False - self.helper.add_input(Submit('submit', 'Submit')) + self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) + self.helper.add_input(Submit('save and submit', 'Save and Submit')) ''' # DEPRECATED diff --git a/compsocsite/mentors/migrations/0007_auto_20191121_1153.py b/compsocsite/mentors/migrations/0007_auto_20191121_1153.py new file mode 100644 index 00000000..cff432fb --- /dev/null +++ b/compsocsite/mentors/migrations/0007_auto_20191121_1153.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.1 on 2019-11-21 16:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0006_auto_20191120_2236'), + ] + + operations = [ + migrations.AddField( + model_name='mentor', + name='other_times', + field=models.CharField(default='', max_length=1000), + ), + migrations.AddField( + model_name='mentor', + name='relevant_info', + field=models.CharField(default='', max_length=1000), + ), + migrations.AlterField( + model_name='mentor', + name='time_slots', + field=models.CharField(default='[]', max_length=1000), + ), + ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index a2bce7c7..d62abcdf 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -221,6 +221,7 @@ class Mentor(models.Model): ('3', 'No Preference'), ) compensation = models.CharField(max_length=1, choices = compensation_choice, default='1') + ''' time_slots_choices = ( ('M_4:00-4:50PM', 'M 4:00-4:50PM'), ('M_4:00-5:50PM', 'M 4:00-5:50PM'), @@ -241,7 +242,13 @@ class Mentor(models.Model): ('T_4:00-5:50AM', 'T 4:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-7:50PM'), ) - time_slots = models.CharField(choices = time_slots_choices, max_length = len(time_slots_choices), default= "") + time_slots = models.CharField(choices = time_slots_choices, max_length = 20, default= "") + ''' + time_slots = models.CharField(max_length = 1000, default= "[]") + + other_times = models.CharField(max_length = 1000, default = "") + relevant_info = models.CharField(max_length = 1000, default = "") + # Course preference of applicants, the data model here is dictionary # Yeah we can do charfield... will change it afterwards diff --git a/compsocsite/mentors/templates/mentors/apply.html b/compsocsite/mentors/templates/mentors/apply.html index c50031c8..8eab6c85 100755 --- a/compsocsite/mentors/templates/mentors/apply.html +++ b/compsocsite/mentors/templates/mentors/apply.html @@ -18,7 +18,6 @@

        {% if not applied %} -
        @@ -33,7 +32,7 @@
        {% else %} -
        Session Expired
        +
        Please Login
        {% endif %} {% endif %} {% endblock %} diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index 5b0ff27a..e12a0291 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -20,13 +20,13 @@
        - Mentor Application + Spring 2020 Undergraduate Programming Mentor Application Form
        {% if applied %} - + Mentor apllication success @@ -35,6 +35,37 @@ {% else %} + +

        + The Computer Science Department is currently recruiting undergraduate + programming mentors for Spring 2020 courses! +

        + +

        + Undergraduate programming mentors help students better understand computer science and, + in particular, programming. + Mentoring typically occurs during labs and recitations, with either the instructor or + TA heading each session. + Mentors also typically hold group office hours. + And for some courses, mentors meet periodically + with TAs and instructors to receive guidance as to how to best mentor. +

        + +

        + Because mentoring for CSCI requires approximately six hours per week + on top of your other commitments, + please do not mentor for other departments + (e.g., the I-PERSIST mentoring program). +

        + +

        + Please apply below. + Applications are due by 12/6/2019. + Send any questions + to Shianne Hulbert. + And please be sure to specify your schedule below to the best of your knowledge. +

        + Begin to apply: {% endif %} diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 2190de2c..a254070c 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -253,11 +253,18 @@ def applystep4(request): # Time slots page def applystep5(request): - form = MentorApplicationfoForm_step5(request.POST or None, initial={}) + initial={ + 'time_slots': request.session.get('time_slots', None), + 'other_times': request.session.get('other_times', None), + } + + form = MentorApplicationfoForm_step5(request.POST or None, initial=initial) if request.method == 'POST': if form.is_valid(): #order_str = breakties(request.POST['pref_order']) - submit_application(request) + request.session['time_slots'] = form.cleaned_data['time_slots'] + request.session['other_times'] = form.cleaned_data['other_times'] + return HttpResponseRedirect(reverse('mentors:applystep6')) else: print(form.errors) @@ -265,10 +272,14 @@ def applystep5(request): # Students Additional Page def applystep6(request): - form = MentorApplicationfoForm_step6(request.POST or None, initial={}) + initial={ + 'relevant_info': request.session.get('relevant_info', None), + } + form = MentorApplicationfoForm_step6(request.POST or None, initial=initial) if request.method == 'POST': if form.is_valid(): #order_str = breakties(request.POST['pref_order']) + request.session['relevant_info'] = form.cleaned_data['relevant_info'] submit_application(request) return render(request, 'mentors/index.html',{}) else: @@ -304,7 +315,13 @@ def submit_application(request): #pref.save() new_applicant.course_pref = breakties(request.session["pref_order"]) + new_applicant.time_slots = request.session["time_slots"] + new_applicant.other_times = request.session["other_times"] + new_applicant.relevant_info = request.session["relevant_info"] + new_applicant.save() + for i in new_applicant.time_slots: + print(i) #orderStr = self.cleaned_data["pref_order"] # Save Grades on the course average From ebd6d5fef3a67bda059e8e9a41edcb89b22f2e33 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Thu, 21 Nov 2019 14:31:34 -0500 Subject: [PATCH 21/44] changes --- compsocsite/mentors/forms.py | 32 ++++++++++++++++--- .../migrations/0008_auto_20191121_1347.py | 23 +++++++++++++ .../migrations/0009_auto_20191121_1348.py | 23 +++++++++++++ .../migrations/0010_auto_20191121_1403.py | 23 +++++++++++++ compsocsite/mentors/models.py | 16 +++++++--- .../mentors/templates/mentors/index.html | 27 ++++++++-------- 6 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 compsocsite/mentors/migrations/0008_auto_20191121_1347.py create mode 100644 compsocsite/mentors/migrations/0009_auto_20191121_1348.py create mode 100644 compsocsite/mentors/migrations/0010_auto_20191121_1403.py diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 7fcc3ae2..84d53438 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -64,7 +64,7 @@ class MentorApplicationfoForm_step2(ModelForm): class Meta: model = Mentor #fields = '__all__' - fields = ( 'compensation',) + fields = ( 'compensation', 'studnet_status', 'employed_paid_before') def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step2, self).__init__(*args, **kwargs) self.helper = FormHelper() @@ -78,7 +78,19 @@ def __init__(self, *args, **kwargs): """), HTML("""
        """), + + HTML(""""""), Div('compensation', css_class = "inputline"), + HTML("""
        """), + + HTML(""""""), + Div('employed_paid_before', css_class = "inputline"), + HTML("""
        """), + + HTML(""""""), + Div('studnet_status', css_class = "inputline"), + HTML("""
        """), + HTML("""
        """), HTML("""
        For a paid position, you must follow the given instructions to ensure you are paid; otherwise, another candidate will be selected. More specifically, you will be required to have a student employment card. This requires @@ -99,6 +111,7 @@ def __init__(self, *args, **kwargs): ) self.helper.form_method = 'POST' self.helper.label_class = "my_label" + self.helper.form_show_labels = False self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) self.helper.add_input(Submit('next', 'Next')) @@ -138,9 +151,9 @@ def __init__(self, *args, **kwargs): self.fields[course.name+ "_exp"] = forms.ChoiceField(choices = choices_YN, label = "") #self.initial[course.name+ "_exp"] = 'N' - course_layout.append(HTML("""""")) + course_layout.append(HTML("""""")) course_layout.append(Field(course.name+ "_grade", label_class="float: left;")) - #course_layout.append(HTML("""
        """)) + #course_layout.append(HTML("""
        """)) course_layout.append(HTML("""""")) course_layout.append(Field(course.name+ "_exp")) course_layout.append(HTML("""
        """)), @@ -149,6 +162,17 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( HTML('== COURSE EXPERIENCE =='), HTML("
        "), + HTML('''
        Below is a list of courses likely to need mentors. + Please check all courses for which you are able to mentor. Note that CSCI 1100 is in + Python, CSCI 1190 is in MATLAB, CSCI 1200 is in C++, CSCI 2300 is in Python/C++, CSCI 2500 + is in C/Assembly, and CSCI 2600 is in Java. For each of the courses you have taken, + please specify the letter grade you earned in the course at RPI (or select "AP" if you + earned AP credit for CSCI 1100).
        '''), + HTML('''
        Note that you cannot be a mentor for a course you will be taking in the same semester or for a course + you plan to take in the future. In some cases, you are allowed to mentor for courses you have never taken; + be sure to describe equivalent courses or experiences in the general comments textbox further below. +
        '''), + HTML("""
        """), course_layout, HTML("
        "), ) @@ -286,7 +310,7 @@ def __init__(self, *args, **kwargs): self.helper.form_method = 'POST' self.helper.form_show_labels = False self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) - self.helper.add_input(Submit('save and submit', 'Save and Submit')) + self.helper.add_input(Submit("save and submit", "Save and Submit")) ''' # DEPRECATED diff --git a/compsocsite/mentors/migrations/0008_auto_20191121_1347.py b/compsocsite/mentors/migrations/0008_auto_20191121_1347.py new file mode 100644 index 00000000..8435b5a9 --- /dev/null +++ b/compsocsite/mentors/migrations/0008_auto_20191121_1347.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.1 on 2019-11-21 18:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0007_auto_20191121_1153'), + ] + + operations = [ + migrations.AddField( + model_name='mentor', + name='employed_paid_before', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='mentor', + name='studnet_status', + field=models.CharField(choices=[('1', 'International'), ('2', 'Domestic')], default='1', max_length=1), + ), + ] diff --git a/compsocsite/mentors/migrations/0009_auto_20191121_1348.py b/compsocsite/mentors/migrations/0009_auto_20191121_1348.py new file mode 100644 index 00000000..4d811149 --- /dev/null +++ b/compsocsite/mentors/migrations/0009_auto_20191121_1348.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.1 on 2019-11-21 18:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0008_auto_20191121_1347'), + ] + + operations = [ + migrations.AlterField( + model_name='mentor', + name='compensation', + field=models.CharField(choices=[('pay', 'Pay ($14/hour) '), ('credit', 'Credit'), ('no_pref', 'No Preference')], default='no_pref', max_length=1), + ), + migrations.AlterField( + model_name='mentor', + name='studnet_status', + field=models.CharField(choices=[('international', 'International'), ('domestic', 'Domestic')], default='international', max_length=1), + ), + ] diff --git a/compsocsite/mentors/migrations/0010_auto_20191121_1403.py b/compsocsite/mentors/migrations/0010_auto_20191121_1403.py new file mode 100644 index 00000000..42bb8512 --- /dev/null +++ b/compsocsite/mentors/migrations/0010_auto_20191121_1403.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.1 on 2019-11-21 19:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0009_auto_20191121_1348'), + ] + + operations = [ + migrations.AlterField( + model_name='mentor', + name='compensation', + field=models.CharField(choices=[('p', 'Pay ($14/hour) '), ('c', 'Credit'), ('n', 'No Preference')], default='no_pref', max_length=1), + ), + migrations.AlterField( + model_name='mentor', + name='studnet_status', + field=models.CharField(choices=[('i', 'International'), ('d', 'Domestic')], default='international', max_length=1), + ), + ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index d62abcdf..1284bbe4 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -214,13 +214,21 @@ class Mentor(models.Model): email = models.CharField(max_length=50) phone = models.CharField(max_length=50) # ??? recommender = models.CharField(max_length=50) + # Compensation Choices compensation_choice = ( - ('1', 'Pay ($14/hour) '), - ('2', 'Credit'), - ('3', 'No Preference'), + ('n', 'No Preference'), + ('p', 'Pay ($14/hour) '), + ('c', 'Credit'), ) - compensation = models.CharField(max_length=1, choices = compensation_choice, default='1') + compensation = models.CharField(max_length=1, choices = compensation_choice, default='n') + status = ( + ('i', 'International'), + ('d', 'Domestic'), + ) + studnet_status = models.CharField(max_length=1, choices = status, default='i') + employed_paid_before = models.BooleanField(default = False) + ''' time_slots_choices = ( ('M_4:00-4:50PM', 'M 4:00-4:50PM'), diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index e12a0291..cad21ce6 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -24,18 +24,6 @@
        -
        Apply
        - {% if applied %} - - - Mentor apllication success - - - - - - {% else %} -

        The Computer Science Department is currently recruiting undergraduate programming mentors for Spring 2020 courses! @@ -62,14 +50,25 @@ Please apply below. Applications are due by 12/6/2019. Send any questions - to Shianne Hulbert. + to Shianne Hulbert. And please be sure to specify your schedule below to the best of your knowledge.

        + {% if applied %} + + + Mentor apllication success + + + + + + {% else %} + + Begin to apply: {% endif %} -
        View
        View
        Apply
        From 72e294bb878fdc4fa6973416327c8ee02bb9fe47 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Fri, 22 Nov 2019 14:50:46 -0500 Subject: [PATCH 22/44] Add new app crispy form --- compsocsite/compsocsite/settings.py | 191 ++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100755 compsocsite/compsocsite/settings.py diff --git a/compsocsite/compsocsite/settings.py b/compsocsite/compsocsite/settings.py new file mode 100755 index 00000000..11556f8f --- /dev/null +++ b/compsocsite/compsocsite/settings.py @@ -0,0 +1,191 @@ +""" +Django settings for compsocsite project. + +Generated by 'django-admin startproject' using Django 1.9.5. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.9/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '_' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'polls.apps.PollsConfig', + 'crispy_forms', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'compsocsite', + 'appauth', + 'groups', + 'multipolls', + 'mentors', + 'django_mobile', + 'mathfilters', + 'sessions_local', + 'corsheaders', + 'qr_code', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django_mobile.middleware.MobileDetectionMiddleware', + 'django_mobile.middleware.SetFlavourMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', +] + +ROOT_URLCONF = 'compsocsite.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'django_mobile.context_processors.flavour', + ], + 'loaders':( + ('django_mobile.loader.CachedLoader', ( + 'django_mobile.loader.Loader', + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + 'django.template.loaders.app_directories.Loader', + )), + ) + }, + }, +] + +WSGI_APPLICATION = 'compsocsite.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', + 'LOCATION': 'my_cache_table', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +#CAS +AUTHENTICATION_BACKENDS = [ + 'django.contrib.auth.backends.ModelBackend', +] +CAS_GATEWAY = True + +CAS_RESPONSE_CALLBACKS = ( + 'module.callbackfunction', +) + +# Internationalization +# https://docs.djangoproject.com/en/1.9/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'EST' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +#CAS +CAS_SERVER_URL = "https://cas-auth.rpi.edu/cas/" +CAS_LOGOUT_COMPLETELY = True +CAS_PROVIDE_URL_TO_LOGOUT = True +CAS_AUTO_CREATE_USERS = True +CAS_IGNORE_REFERER = True +CAS_REDIRECT_URL = '/polls/regular_polls' +#'https://opra.cs.rpi.edu' +# CAS_FORCE_SSL_SERVICE_URL = True + +################################################# +# Email settings # +################################################# +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_USE_TLS = True +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_HOST_USER = 'oprahprogramtest@gmail.com' +EMAIL_HOST_PASSWORD = 'ThisIsJustATestProgram' +EMAIL_PORT = 587 + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.9/howto/static-files/ + +STATIC_URL = '/static/' +LOGIN_URL = '/auth/login/' + +STATICFILES_DIRS = ( + '/static/', + os.path.join(os.path.abspath(BASE_DIR), 'static'), +) + + +# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +# SESSION_COOKIE_SECURE = True +# CSRF_COOKIE_SECURE = True +# SECURE_HSTS_SECONDS = 3600 +# SECURE_SSL_REDIRECT = False From 999bb88ec5adec56b3dcf90087999359d1908e8a Mon Sep 17 00:00:00 2001 From: starwarswii Date: Fri, 22 Nov 2019 14:59:25 -0500 Subject: [PATCH 23/44] updated readme also added old auth , commented out, for reference --- README.md | 3 ++- compsocsite/compsocsite/settings.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 10361707..cf168a66 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The web app is built on Django, and uses an SQLite database. [Click here](https: 5. Clone this project from Github -6. Copy settings.py from opra_dependencies to compsocsite/compsocsite. Copy opra_crypto.py from opra_dependencies to compsocsite/polls. +6. ~~Copy settings.py from opra_dependencies to compsocsite/compsocsite.~~ (an updated settings.py is now included.) Copy opra_crypto.py from opra_dependencies to compsocsite/polls. 7. Open command line (terminal), change to OPRA's directory, and then enter the following commands: @@ -58,6 +58,7 @@ The web app is built on Django, and uses an SQLite database. [Click here](https: * **scipy**: * **numpy**: * **networkx**: +* **django-crispy-forms**: ##Models diff --git a/compsocsite/compsocsite/settings.py b/compsocsite/compsocsite/settings.py index 11556f8f..abd4b3c5 100755 --- a/compsocsite/compsocsite/settings.py +++ b/compsocsite/compsocsite/settings.py @@ -131,6 +131,7 @@ #CAS AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', + #'cas.backends.CASBackend', ] CAS_GATEWAY = True From a31a5c2667412aebb23526f12e39ef7b00e5bfc7 Mon Sep 17 00:00:00 2001 From: starwarswii Date: Fri, 22 Nov 2019 15:07:05 -0500 Subject: [PATCH 24/44] Update README.md mentioned views.py change, and better formatting --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cf168a66..138162ae 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,15 @@ The web app is built on Django, and uses an SQLite database. [Click here](https: 6. ~~Copy settings.py from opra_dependencies to compsocsite/compsocsite.~~ (an updated settings.py is now included.) Copy opra_crypto.py from opra_dependencies to compsocsite/polls. +6.5. For testing locally, edit compsocsite/appauth/views.py and find-replace `https://opra.cs.rpi.edu` with `http://127.0.0.1:8000` + 7. Open command line (terminal), change to OPRA's directory, and then enter the following commands: - cd composcite - - python manage.py migrate - - python manage.py createcachetable + ``` + cd composcite + python manage.py migrate + python manage.py createcachetable + ``` Then run the server by entering: From 694db98daeece10a949303603c0acaa4726eb1c8 Mon Sep 17 00:00:00 2001 From: starwarswii Date: Fri, 22 Nov 2019 15:44:33 -0500 Subject: [PATCH 25/44] added a basic banner on the main page right now link just goes to google. will need to be replaced. --- compsocsite/polls/templates/polls/index.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compsocsite/polls/templates/polls/index.html b/compsocsite/polls/templates/polls/index.html index 84aa6d1d..69ab3a87 100755 --- a/compsocsite/polls/templates/polls/index.html +++ b/compsocsite/polls/templates/polls/index.html @@ -29,6 +29,11 @@ + + +
        From 30572e311e9331a30b81953dd84591704d5d8aca Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 23 Nov 2019 10:49:40 -0500 Subject: [PATCH 26/44] Link mentor app to each account --- .../appauth/migrations/0001_initial.py | 23 +- .../migrations/0002_auto_20191123_1035.py | 19 ++ .../0002_userprofile_displaypref.py | 20 -- .../migrations/0003_auto_20160702_1713.py | 35 --- .../migrations/0004_userprofile_showhint.py | 20 -- .../migrations/0005_auto_20170306_1628.py | 35 --- .../migrations/0006_auto_20171117_1007.py | 25 -- .../migrations/0007_userprofile_code.py | 20 -- .../migrations/0008_userprofile_comments.py | 20 -- .../migrations/0009_auto_20171204_1448.py | 20 -- .../migrations/0010_auto_20171216_1342.py | 25 -- .../migrations/0011_auto_20171224_2353.py | 27 -- .../migrations/0012_auto_20171224_2356.py | 22 -- .../migrations/0013_auto_20171229_1132.py | 22 -- .../migrations/0014_auto_20171229_1135.py | 20 -- .../migrations/0015_userprofile_finished.py | 20 -- .../migrations/0016_userprofile_numq.py | 20 -- .../migrations/0017_userprofile_exp_data.py | 20 -- compsocsite/appauth/models.py | 11 +- compsocsite/appauth/views.py | 8 +- compsocsite/mentors/forms.py | 79 +++-- .../mentors/migrations/0001_initial.py | 19 +- .../migrations/0002_auto_20191113_2042.py | 18 -- .../migrations/0003_course_mentor_cap.py | 18 -- .../migrations/0004_auto_20191116_1454.py | 18 -- .../migrations/0005_auto_20191120_2212.py | 23 -- .../migrations/0006_auto_20191120_2236.py | 18 -- .../migrations/0007_auto_20191121_1153.py | 28 -- .../migrations/0008_auto_20191121_1347.py | 23 -- .../migrations/0009_auto_20191121_1348.py | 23 -- .../migrations/0010_auto_20191121_1403.py | 23 -- compsocsite/mentors/models.py | 8 +- .../mentors/templates/mentors/index.html | 17 +- compsocsite/mentors/urls.py | 12 +- compsocsite/mentors/views.py | 270 ++++++++++++------ 35 files changed, 327 insertions(+), 702 deletions(-) mode change 100755 => 100644 compsocsite/appauth/migrations/0001_initial.py create mode 100644 compsocsite/appauth/migrations/0002_auto_20191123_1035.py delete mode 100755 compsocsite/appauth/migrations/0002_userprofile_displaypref.py delete mode 100755 compsocsite/appauth/migrations/0003_auto_20160702_1713.py delete mode 100644 compsocsite/appauth/migrations/0004_userprofile_showhint.py delete mode 100644 compsocsite/appauth/migrations/0005_auto_20170306_1628.py delete mode 100755 compsocsite/appauth/migrations/0006_auto_20171117_1007.py delete mode 100644 compsocsite/appauth/migrations/0007_userprofile_code.py delete mode 100644 compsocsite/appauth/migrations/0008_userprofile_comments.py delete mode 100644 compsocsite/appauth/migrations/0009_auto_20171204_1448.py delete mode 100644 compsocsite/appauth/migrations/0010_auto_20171216_1342.py delete mode 100644 compsocsite/appauth/migrations/0011_auto_20171224_2353.py delete mode 100644 compsocsite/appauth/migrations/0012_auto_20171224_2356.py delete mode 100644 compsocsite/appauth/migrations/0013_auto_20171229_1132.py delete mode 100644 compsocsite/appauth/migrations/0014_auto_20171229_1135.py delete mode 100644 compsocsite/appauth/migrations/0015_userprofile_finished.py delete mode 100644 compsocsite/appauth/migrations/0016_userprofile_numq.py delete mode 100644 compsocsite/appauth/migrations/0017_userprofile_exp_data.py delete mode 100644 compsocsite/mentors/migrations/0002_auto_20191113_2042.py delete mode 100644 compsocsite/mentors/migrations/0003_course_mentor_cap.py delete mode 100644 compsocsite/mentors/migrations/0004_auto_20191116_1454.py delete mode 100644 compsocsite/mentors/migrations/0005_auto_20191120_2212.py delete mode 100644 compsocsite/mentors/migrations/0006_auto_20191120_2236.py delete mode 100644 compsocsite/mentors/migrations/0007_auto_20191121_1153.py delete mode 100644 compsocsite/mentors/migrations/0008_auto_20191121_1347.py delete mode 100644 compsocsite/mentors/migrations/0009_auto_20191121_1348.py delete mode 100644 compsocsite/mentors/migrations/0010_auto_20191121_1403.py diff --git a/compsocsite/appauth/migrations/0001_initial.py b/compsocsite/appauth/migrations/0001_initial.py old mode 100755 new mode 100644 index cab0b287..667034b8 --- a/compsocsite/appauth/migrations/0001_initial.py +++ b/compsocsite/appauth/migrations/0001_initial.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.6 on 2016-06-08 21:00 -from __future__ import unicode_literals +# Generated by Django 2.2.1 on 2019-11-22 21:31 from django.conf import settings from django.db import migrations, models @@ -12,6 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('mentors', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -20,6 +19,24 @@ class Migration(migrations.Migration): name='UserProfile', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('time_creation', models.DateTimeField()), + ('displayPref', models.IntegerField(default=1)), + ('emailInvite', models.BooleanField(default=False)), + ('emailDelete', models.BooleanField(default=False)), + ('emailStart', models.BooleanField(default=False)), + ('emailStop', models.BooleanField(default=False)), + ('showHint', models.BooleanField(default=True)), + ('mturk', models.IntegerField(default=0)), + ('age', models.IntegerField(default=0)), + ('code', models.CharField(blank=True, max_length=100, null=True)), + ('comments', models.CharField(blank=True, max_length=1000, null=True)), + ('sequence', models.TextField(default='')), + ('cur_poll', models.IntegerField(default=1)), + ('finished', models.BooleanField(default=False)), + ('numq', models.IntegerField(default=0)), + ('exp_data', models.TextField(default='{}')), + ('mentor_applied', models.BooleanField(default=False)), + ('mentor_profile', models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='mentors.Mentor')), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), diff --git a/compsocsite/appauth/migrations/0002_auto_20191123_1035.py b/compsocsite/appauth/migrations/0002_auto_20191123_1035.py new file mode 100644 index 00000000..51e35823 --- /dev/null +++ b/compsocsite/appauth/migrations/0002_auto_20191123_1035.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.1 on 2019-11-23 15:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='mentor_profile', + field=models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mentors.Mentor'), + ), + ] diff --git a/compsocsite/appauth/migrations/0002_userprofile_displaypref.py b/compsocsite/appauth/migrations/0002_userprofile_displaypref.py deleted file mode 100755 index 1452c3e6..00000000 --- a/compsocsite/appauth/migrations/0002_userprofile_displaypref.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.6 on 2016-06-20 20:41 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='displayPref', - field=models.IntegerField(default=1), - ), - ] diff --git a/compsocsite/appauth/migrations/0003_auto_20160702_1713.py b/compsocsite/appauth/migrations/0003_auto_20160702_1713.py deleted file mode 100755 index 012d8291..00000000 --- a/compsocsite/appauth/migrations/0003_auto_20160702_1713.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-02 21:13 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0002_userprofile_displaypref'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='emailDelete', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='userprofile', - name='emailInvite', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='userprofile', - name='emailStart', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='userprofile', - name='emailStop', - field=models.BooleanField(default=True), - ), - ] diff --git a/compsocsite/appauth/migrations/0004_userprofile_showhint.py b/compsocsite/appauth/migrations/0004_userprofile_showhint.py deleted file mode 100644 index 6dfb7520..00000000 --- a/compsocsite/appauth/migrations/0004_userprofile_showhint.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.6 on 2016-08-09 00:19 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0003_auto_20160702_1713'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='showHint', - field=models.BooleanField(default=True), - ), - ] diff --git a/compsocsite/appauth/migrations/0005_auto_20170306_1628.py b/compsocsite/appauth/migrations/0005_auto_20170306_1628.py deleted file mode 100644 index 5466f063..00000000 --- a/compsocsite/appauth/migrations/0005_auto_20170306_1628.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2017-03-06 21:28 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0004_userprofile_showhint'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='emailDelete', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='userprofile', - name='emailInvite', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='userprofile', - name='emailStart', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='userprofile', - name='emailStop', - field=models.BooleanField(default=False), - ), - ] diff --git a/compsocsite/appauth/migrations/0006_auto_20171117_1007.py b/compsocsite/appauth/migrations/0006_auto_20171117_1007.py deleted file mode 100755 index 208699e6..00000000 --- a/compsocsite/appauth/migrations/0006_auto_20171117_1007.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-11-17 15:07 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0005_auto_20170306_1628'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='age', - field=models.IntegerField(default=0), - ), - migrations.AddField( - model_name='userprofile', - name='mturk', - field=models.IntegerField(default=0), - ), - ] diff --git a/compsocsite/appauth/migrations/0007_userprofile_code.py b/compsocsite/appauth/migrations/0007_userprofile_code.py deleted file mode 100644 index 70f3ac67..00000000 --- a/compsocsite/appauth/migrations/0007_userprofile_code.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9 on 2017-11-30 01:07 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0006_auto_20171117_1007'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='code', - field=models.IntegerField(default=0), - ), - ] diff --git a/compsocsite/appauth/migrations/0008_userprofile_comments.py b/compsocsite/appauth/migrations/0008_userprofile_comments.py deleted file mode 100644 index af73f533..00000000 --- a/compsocsite/appauth/migrations/0008_userprofile_comments.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9 on 2017-12-01 03:34 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0007_userprofile_code'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='comments', - field=models.CharField(blank=True, max_length=1000, null=True), - ), - ] diff --git a/compsocsite/appauth/migrations/0009_auto_20171204_1448.py b/compsocsite/appauth/migrations/0009_auto_20171204_1448.py deleted file mode 100644 index a2998aab..00000000 --- a/compsocsite/appauth/migrations/0009_auto_20171204_1448.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9 on 2017-12-04 19:48 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0008_userprofile_comments'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='code', - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/compsocsite/appauth/migrations/0010_auto_20171216_1342.py b/compsocsite/appauth/migrations/0010_auto_20171216_1342.py deleted file mode 100644 index 97ee3622..00000000 --- a/compsocsite/appauth/migrations/0010_auto_20171216_1342.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-12-16 18:42 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0009_auto_20171204_1448'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='cur_poll', - field=models.IntegerField(default=369), - ), - migrations.AddField( - model_name='userprofile', - name='sequence', - field=models.TextField(default=''), - ), - ] diff --git a/compsocsite/appauth/migrations/0011_auto_20171224_2353.py b/compsocsite/appauth/migrations/0011_auto_20171224_2353.py deleted file mode 100644 index 30067387..00000000 --- a/compsocsite/appauth/migrations/0011_auto_20171224_2353.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-12-25 04:53 -from __future__ import unicode_literals - -import datetime -from django.db import migrations, models -from django.utils.timezone import utc - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0010_auto_20171216_1342'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='time_creation', - field=models.DateTimeField(default=datetime.datetime(2017, 12, 25, 4, 52, 59, 194669, tzinfo=utc), verbose_name='time_created'), - ), - migrations.AlterField( - model_name='userprofile', - name='cur_poll', - field=models.IntegerField(default=1), - ), - ] diff --git a/compsocsite/appauth/migrations/0012_auto_20171224_2356.py b/compsocsite/appauth/migrations/0012_auto_20171224_2356.py deleted file mode 100644 index 30a98ac4..00000000 --- a/compsocsite/appauth/migrations/0012_auto_20171224_2356.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-12-25 04:56 -from __future__ import unicode_literals - -import datetime -from django.db import migrations, models -from django.utils.timezone import utc - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0011_auto_20171224_2353'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='time_creation', - field=models.DateTimeField(verbose_name=datetime.datetime(2017, 12, 25, 4, 56, 19, 179450, tzinfo=utc)), - ), - ] diff --git a/compsocsite/appauth/migrations/0013_auto_20171229_1132.py b/compsocsite/appauth/migrations/0013_auto_20171229_1132.py deleted file mode 100644 index 59c0cd46..00000000 --- a/compsocsite/appauth/migrations/0013_auto_20171229_1132.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-12-29 16:32 -from __future__ import unicode_literals - -import datetime -from django.db import migrations, models -from django.utils.timezone import utc - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0012_auto_20171224_2356'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='time_creation', - field=models.DateTimeField(verbose_name=datetime.datetime(2017, 12, 29, 16, 32, 26, 643080, tzinfo=utc)), - ), - ] diff --git a/compsocsite/appauth/migrations/0014_auto_20171229_1135.py b/compsocsite/appauth/migrations/0014_auto_20171229_1135.py deleted file mode 100644 index eb0c6fe4..00000000 --- a/compsocsite/appauth/migrations/0014_auto_20171229_1135.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-12-29 16:35 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0013_auto_20171229_1132'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='time_creation', - field=models.DateTimeField(), - ), - ] diff --git a/compsocsite/appauth/migrations/0015_userprofile_finished.py b/compsocsite/appauth/migrations/0015_userprofile_finished.py deleted file mode 100644 index 287b8995..00000000 --- a/compsocsite/appauth/migrations/0015_userprofile_finished.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-12-30 19:26 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0014_auto_20171229_1135'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='finished', - field=models.BooleanField(default=False), - ), - ] diff --git a/compsocsite/appauth/migrations/0016_userprofile_numq.py b/compsocsite/appauth/migrations/0016_userprofile_numq.py deleted file mode 100644 index 11528e62..00000000 --- a/compsocsite/appauth/migrations/0016_userprofile_numq.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-01-06 16:41 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0015_userprofile_finished'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='numq', - field=models.IntegerField(default=0), - ), - ] diff --git a/compsocsite/appauth/migrations/0017_userprofile_exp_data.py b/compsocsite/appauth/migrations/0017_userprofile_exp_data.py deleted file mode 100644 index a126beb1..00000000 --- a/compsocsite/appauth/migrations/0017_userprofile_exp_data.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2018-01-24 07:34 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0016_userprofile_numq'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='exp_data', - field=models.TextField(default='{}'), - ), - ] diff --git a/compsocsite/appauth/models.py b/compsocsite/appauth/models.py index 79b59ff1..d3271cd1 100755 --- a/compsocsite/appauth/models.py +++ b/compsocsite/appauth/models.py @@ -2,6 +2,8 @@ import datetime import django + +from mentors.models import Mentor from django import forms from django.db import models from django.utils import timezone @@ -10,7 +12,7 @@ class UserProfile(models.Model): # This line is required. Links UserProfile to a User model instance. - user = models.OneToOneField(User,on_delete=models.CASCADE,) + user = models.OneToOneField(User, on_delete = models.CASCADE,) time_creation = models.DateTimeField() displayPref = models.IntegerField(default=1) emailInvite = models.BooleanField(default=False) @@ -27,6 +29,13 @@ class UserProfile(models.Model): finished = models.BooleanField(default=False) numq= models.IntegerField(default=0) exp_data = models.TextField(default="{}") + + # Mentor Applicaton Profile + mentor_applied = models.BooleanField(default = False) + + # Set mentor reference to SET_NULL here to protect the deletion of the user profile + mentor_profile = models.OneToOneField(Mentor, on_delete = models.SET_NULL, default = None, null = True) + # Override the __unicode__() method to return out something meaningful! def __unicode__(self): return self.user.username diff --git a/compsocsite/appauth/views.py b/compsocsite/appauth/views.py index d6d7027a..49885f5e 100755 --- a/compsocsite/appauth/views.py +++ b/compsocsite/appauth/views.py @@ -52,7 +52,7 @@ def register(request): # Update our variable to tell the template registration was successful. registered = True - htmlstr = "

        Click This Link To Activate Your Account

        " + htmlstr = "

        Click This Link To Activate Your Account

        " mail.send_mail("OPRA Confirmation","Please confirm your account registration.",'oprahprogramtest@gmail.com',[user.email],html_message=htmlstr) #else print (user_form.errors) else: @@ -99,7 +99,7 @@ def quickRegister(request, question_id): # Update our variable to tell the template registration was successful. registered = True - htmlstr = "

        Click This Link To Activate Your Account

        " + htmlstr = "

        Click This Link To Activate Your Account

        " mail.send_mail("OPRA Confirmation","Please confirm your account registration.",'oprahprogramtest@gmail.com',[user.email],html_message=htmlstr) #else print (user_form.errors) else: @@ -141,7 +141,7 @@ def user_login(request): else: email = user.email if email: - htmlstr = "Please CLICK HERE to activate your account." + htmlstr = "Please CLICK HERE to activate your account." mail.send_mail("OPRA Confirmation From Invalid Login","Please confirm your account registration.",'oprahprogramtest@gmail.com',[email],html_message=htmlstr) return HttpResponse("Your account is not active. We have resent an activation link to your email address. Please check.") else: @@ -249,7 +249,7 @@ def forgetPassword(request): if email == "" or username == "": return HttpResponseRedirect(request.META.get('HTTP_REFERER')) user = get_object_or_404(User, email=email, username=username) - htmlstr = "

        Click This Link To Reset Password

        " + htmlstr = "

        Click This Link To Reset Password

        " mail.send_mail("OPRA Forget Password","Please click the following link to reset password.",'oprahprogramtest@gmail.com',[email],html_message=htmlstr) return HttpResponse("An email has been sent to your email account. Please click on the link in that email and reset your password.") diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 84d53438..bbfbefa0 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -29,7 +29,7 @@ class Meta: def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step1, self).__init__(*args, **kwargs) self.helper = FormHelper() - #self.fields['email'] = user.email + self.fields['recommender'].required = False self.helper.layout = Layout( HTML('== PERSONAL INFORMATION =='), HTML("
        "), @@ -137,6 +137,7 @@ def __init__(self, *args, **kwargs): ('d', 'D'), ('f', 'F'), ('p', 'Progressing'), + ('ap', 'AP'), ('n', 'Not Taken'), ) choices_YN = ( @@ -162,8 +163,7 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( HTML('== COURSE EXPERIENCE =='), HTML("
        "), - HTML('''
        Below is a list of courses likely to need mentors. - Please check all courses for which you are able to mentor. Note that CSCI 1100 is in + HTML('''
        Below is a list of courses likely to need mentors. Note that CSCI 1100 is in Python, CSCI 1190 is in MATLAB, CSCI 1200 is in C++, CSCI 2300 is in Python/C++, CSCI 2500 is in C/Assembly, and CSCI 2600 is in Java. For each of the courses you have taken, please specify the letter grade you earned in the course at RPI (or select "AP" if you @@ -193,25 +193,66 @@ def __init__(self, *args, **kwargs): HTML('== COURSE PREFERENCE =='), HTML("
        "), - HTML('''
        Please rank the courses which you prefer to mentor, #1 means the highest prioity, and #2 means the second priority...etc. The rankings will help us to allocate your position.
        '''), + HTML('''
        Select the courses you would like to mentor and the courses + you do not prefer. For the course you would like to mentor, please change their rankings by + dragging the courses. + #1 means the highest prioity for you to mentor, and #2 means the second priority...etc. +
        '''), + HTML('''
        Your ranking will help us to decide your position. +
        '''), + HTML("""
        """), + # Ranking UI here HTML(''' -
          -
          - {% for course in courses %} - {% if course %} -
            - -
            #{{ forloop.counter }}
            -
          • - {{ course.subject }} {{course.number}} -
          • -
          - {% endif %} - {% endfor %} -
        +
        +
        +
        + Courses Prefer to Mentor:   + +
        + +
        +
          + {% for course in pref_courses %} + {% if course %} +
            + +
            {{ forloop.counter }}
            +
          • +   {{ course.subject }} {{ course.number }}   {{course.name }} +
          • +
          + {% endif %} + {% endfor %} +
        +
        +
        +
        +
        +
        +
        + Courses NOT Prefer to Mentor:   + +
        + +
        +
          + {% for course in not_pref_courses %} + {% if course %} +
            +
          • +   {{ course.subject }} {{ course.number }}   {{course.name }} +
          • +
          + {% endif %} + {% endfor %} +
        +
        +
        +
        '''), # HTML part to store thr rankings Field('pref_order', id='pref_order', css_class = 'pref_order', type = 'hidden'), diff --git a/compsocsite/mentors/migrations/0001_initial.py b/compsocsite/mentors/migrations/0001_initial.py index f8606291..3c333a27 100644 --- a/compsocsite/mentors/migrations/0001_initial.py +++ b/compsocsite/mentors/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.1 on 2019-11-14 00:42 +# Generated by Django 2.2.1 on 2019-11-22 21:31 import django.core.validators from django.db import migrations, models @@ -26,6 +26,7 @@ class Migration(migrations.Migration): ('feature_has_taken', models.IntegerField(default=0)), ('feature_course_GPA', models.IntegerField(default=0)), ('feature_mentor_exp', models.IntegerField(default=0)), + ('mentor_cap', models.IntegerField(default=0)), ], ), migrations.CreateModel( @@ -47,17 +48,23 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Mentor', fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('applied', models.BooleanField(default=False)), - ('RIN', models.CharField(max_length=9, primary_key=True, serialize=False, validators=[django.core.validators.MinLengthValidator(9)])), + ('RIN', models.CharField(max_length=9, validators=[django.core.validators.MinLengthValidator(9)])), ('first_name', models.CharField(max_length=50)), ('last_name', models.CharField(max_length=50)), ('GPA', mentors.models.MinMaxFloat()), ('email', models.CharField(max_length=50)), ('phone', models.CharField(max_length=50)), - ('recommender', models.CharField(max_length=50)), - ('compensation', models.CharField(choices=[('1', 'Pay'), ('2', 'Credit'), ('3', 'No Preference')], default='1', max_length=1)), - ('course_pref', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='mentors.Dict')), - ('mentor_course', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='mentors.Course')), + ('recommender', models.CharField(default='', max_length=50)), + ('compensation', models.CharField(choices=[('n', 'No Preference'), ('p', 'Pay ($14/hour) '), ('c', 'Credit')], default='n', max_length=1)), + ('studnet_status', models.CharField(choices=[('i', 'International'), ('d', 'Domestic')], default='i', max_length=1)), + ('employed_paid_before', models.BooleanField(default=False)), + ('time_slots', models.CharField(default='[]', max_length=1000)), + ('other_times', models.CharField(default='', max_length=1000)), + ('relevant_info', models.CharField(default='', max_length=1000)), + ('course_pref', models.CharField(max_length=10000)), + ('mentored_course', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='mentors.Course')), ], ), migrations.CreateModel( diff --git a/compsocsite/mentors/migrations/0002_auto_20191113_2042.py b/compsocsite/mentors/migrations/0002_auto_20191113_2042.py deleted file mode 100644 index 812466b8..00000000 --- a/compsocsite/mentors/migrations/0002_auto_20191113_2042.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-14 01:42 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='mentor', - old_name='mentor_course', - new_name='mentored_course', - ), - ] diff --git a/compsocsite/mentors/migrations/0003_course_mentor_cap.py b/compsocsite/mentors/migrations/0003_course_mentor_cap.py deleted file mode 100644 index 99fa617d..00000000 --- a/compsocsite/mentors/migrations/0003_course_mentor_cap.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-14 19:39 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0002_auto_20191113_2042'), - ] - - operations = [ - migrations.AddField( - model_name='course', - name='mentor_cap', - field=models.IntegerField(default=0), - ), - ] diff --git a/compsocsite/mentors/migrations/0004_auto_20191116_1454.py b/compsocsite/mentors/migrations/0004_auto_20191116_1454.py deleted file mode 100644 index 6b62ef3e..00000000 --- a/compsocsite/mentors/migrations/0004_auto_20191116_1454.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-16 19:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0003_course_mentor_cap'), - ] - - operations = [ - migrations.AlterField( - model_name='mentor', - name='course_pref', - field=models.CharField(max_length=10000), - ), - ] diff --git a/compsocsite/mentors/migrations/0005_auto_20191120_2212.py b/compsocsite/mentors/migrations/0005_auto_20191120_2212.py deleted file mode 100644 index 89021094..00000000 --- a/compsocsite/mentors/migrations/0005_auto_20191120_2212.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-21 03:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0004_auto_20191116_1454'), - ] - - operations = [ - migrations.AddField( - model_name='mentor', - name='time_slots', - field=models.CharField(choices=[('M_4:00-4:50PM', 'M 4:00-4:50PM'), ('M_4:00-5:50PM', 'M 4:00-5:50PM'), ('M_5:00-5:50PM', 'M 5:00-5:50PM'), ('M_6:00-6:50PM', 'M 6:00-6:50PM'), ('T_10:00-11:50AM', 'T 10:00-11:50AM'), ('T_12:00-1:50PM', 'T 12:00-1:50PM'), ('T_2:00-3:50PM', 'T 2:00-3:50PM'), ('T_4:00-4:50PM', 'T 4:00-4:50PM'), ('T_5:00-5:50AM', 'T 5:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-6:50PM'), ('W_10:00-11:50AM', 'W 10:00-11:50AM'), ('W_12:00-1:50PM', 'W 12:00-1:50PM'), ('W_2:00-3:50PM', 'W 2:00-3:50PM'), ('W_4:00-4:50PM', 'W 4:00-4:50PM'), ('W_5:00-5:50AM', 'W 5:00-5:50AM'), ('W_6:00-6:50PM', ' W 6:00-7:50PM'), ('T_4:00-5:50AM', 'T 4:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-7:50PM')], default=None, max_length=18), - ), - migrations.AlterField( - model_name='mentor', - name='compensation', - field=models.CharField(choices=[('1', 'Pay ($14/hour) '), ('2', 'Credit'), ('3', 'No Preference')], default='1', max_length=1), - ), - ] diff --git a/compsocsite/mentors/migrations/0006_auto_20191120_2236.py b/compsocsite/mentors/migrations/0006_auto_20191120_2236.py deleted file mode 100644 index 484505a5..00000000 --- a/compsocsite/mentors/migrations/0006_auto_20191120_2236.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-21 03:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0005_auto_20191120_2212'), - ] - - operations = [ - migrations.AlterField( - model_name='mentor', - name='time_slots', - field=models.CharField(choices=[('M_4:00-4:50PM', 'M 4:00-4:50PM'), ('M_4:00-5:50PM', 'M 4:00-5:50PM'), ('M_5:00-5:50PM', 'M 5:00-5:50PM'), ('M_6:00-6:50PM', 'M 6:00-6:50PM'), ('T_10:00-11:50AM', 'T 10:00-11:50AM'), ('T_12:00-1:50PM', 'T 12:00-1:50PM'), ('T_2:00-3:50PM', 'T 2:00-3:50PM'), ('T_4:00-4:50PM', 'T 4:00-4:50PM'), ('T_5:00-5:50AM', 'T 5:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-6:50PM'), ('W_10:00-11:50AM', 'W 10:00-11:50AM'), ('W_12:00-1:50PM', 'W 12:00-1:50PM'), ('W_2:00-3:50PM', 'W 2:00-3:50PM'), ('W_4:00-4:50PM', 'W 4:00-4:50PM'), ('W_5:00-5:50AM', 'W 5:00-5:50AM'), ('W_6:00-6:50PM', ' W 6:00-7:50PM'), ('T_4:00-5:50AM', 'T 4:00-5:50AM'), ('T_6:00-6:50PM', 'T 6:00-7:50PM')], default='', max_length=18), - ), - ] diff --git a/compsocsite/mentors/migrations/0007_auto_20191121_1153.py b/compsocsite/mentors/migrations/0007_auto_20191121_1153.py deleted file mode 100644 index cff432fb..00000000 --- a/compsocsite/mentors/migrations/0007_auto_20191121_1153.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-21 16:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0006_auto_20191120_2236'), - ] - - operations = [ - migrations.AddField( - model_name='mentor', - name='other_times', - field=models.CharField(default='', max_length=1000), - ), - migrations.AddField( - model_name='mentor', - name='relevant_info', - field=models.CharField(default='', max_length=1000), - ), - migrations.AlterField( - model_name='mentor', - name='time_slots', - field=models.CharField(default='[]', max_length=1000), - ), - ] diff --git a/compsocsite/mentors/migrations/0008_auto_20191121_1347.py b/compsocsite/mentors/migrations/0008_auto_20191121_1347.py deleted file mode 100644 index 8435b5a9..00000000 --- a/compsocsite/mentors/migrations/0008_auto_20191121_1347.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-21 18:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0007_auto_20191121_1153'), - ] - - operations = [ - migrations.AddField( - model_name='mentor', - name='employed_paid_before', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='mentor', - name='studnet_status', - field=models.CharField(choices=[('1', 'International'), ('2', 'Domestic')], default='1', max_length=1), - ), - ] diff --git a/compsocsite/mentors/migrations/0009_auto_20191121_1348.py b/compsocsite/mentors/migrations/0009_auto_20191121_1348.py deleted file mode 100644 index 4d811149..00000000 --- a/compsocsite/mentors/migrations/0009_auto_20191121_1348.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-21 18:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0008_auto_20191121_1347'), - ] - - operations = [ - migrations.AlterField( - model_name='mentor', - name='compensation', - field=models.CharField(choices=[('pay', 'Pay ($14/hour) '), ('credit', 'Credit'), ('no_pref', 'No Preference')], default='no_pref', max_length=1), - ), - migrations.AlterField( - model_name='mentor', - name='studnet_status', - field=models.CharField(choices=[('international', 'International'), ('domestic', 'Domestic')], default='international', max_length=1), - ), - ] diff --git a/compsocsite/mentors/migrations/0010_auto_20191121_1403.py b/compsocsite/mentors/migrations/0010_auto_20191121_1403.py deleted file mode 100644 index 42bb8512..00000000 --- a/compsocsite/mentors/migrations/0010_auto_20191121_1403.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-21 19:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0009_auto_20191121_1348'), - ] - - operations = [ - migrations.AlterField( - model_name='mentor', - name='compensation', - field=models.CharField(choices=[('p', 'Pay ($14/hour) '), ('c', 'Credit'), ('n', 'No Preference')], default='no_pref', max_length=1), - ), - migrations.AlterField( - model_name='mentor', - name='studnet_status', - field=models.CharField(choices=[('i', 'International'), ('d', 'Domestic')], default='international', max_length=1), - ), - ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index 1284bbe4..31f62cbd 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -177,6 +177,8 @@ class KeyValuePair(models.Model): key = models.CharField(max_length=240, db_index=True) value = models.CharField(max_length=240, db_index=True) + + # The model for a course class Course(models.Model): subject = models.CharField(max_length=4) # e.g CSCI @@ -207,13 +209,15 @@ class Mentor(models.Model): #step = models.IntegerField(default = 1) # Personal Info & preference of applicants - RIN = models.CharField(max_length=9, validators=[MinLengthValidator(9)], primary_key=True) + #RIN = models.CharField(max_length=9, validators=[MinLengthValidator(9)], primary_key=True) + + RIN = models.CharField(max_length=9, validators=[MinLengthValidator(9)]) first_name = models.CharField(max_length=50) # first name last_name = models.CharField(max_length=50) # last name GPA = MinMaxFloat(min_value = 0.0, max_value = 4.0) email = models.CharField(max_length=50) phone = models.CharField(max_length=50) # ??? - recommender = models.CharField(max_length=50) + recommender = models.CharField(max_length=50, default="") # Compensation Choices compensation_choice = ( diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index cad21ce6..0b505980 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -53,20 +53,21 @@ to Shianne Hulbert. And please be sure to specify your schedule below to the best of your knowledge.

        +
        {% if applied %} - - Mentor apllication success +

        Your mentor application success!

        +

        You can change your application at anytime, but remember, we will start to look at all the applications after 12/6, so make sure you submitted before the deadline.

        + + View your application and make changes:   + View and Change View
        - +
        {% else %} - - - - Begin to apply: + Begin to apply:   Apply {% endif %}
        @@ -111,7 +112,7 @@
        {% csrf_token %} Match: - +
        {% csrf_token %} diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index b3f432b3..9ad3a8e2 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -8,15 +8,15 @@ app_name = 'mentors' urlpatterns = [ - url(r'^$', login_required(views.IndexView.as_view()), name='index'), + url(r'^$', login_required(views.viewindex), name='index'), url(r'^apply$', login_required(views.ApplyView.as_view()), name='apply'), # personal info url(r'^apply/$', views.applystep, name='applyfunc1'), - url(r'^applystep2/$', views.applystep2, name='applystep2'), - url(r'^applystep3/$', views.applystep3, name='applystep3'), - url(r'^applystep4/$', views.applystep4, name='applystep4'), - url(r'^applystep5/$', views.applystep5, name='applystep5'), - url(r'^applystep6/$', views.applystep6, name='applystep6'), + url(r'^applystep2/$', login_required(views.applystep2), name='applystep2'), + url(r'^applystep3/$', login_required(views.applystep3), name='applystep3'), + url(r'^applystep4/$', login_required(views.applystep4), name='applystep4'), + url(r'^applystep5/$', login_required(views.applystep5), name='applystep5'), + url(r'^applystep6/$', login_required(views.applystep6), name='applystep6'), url(r'^view-course$', login_required(views.CourseFeatureView.as_view()), name='view-course'), url(r'^view-course-result$', login_required(views.viewResultPage), name='view-course-result'), diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index a254070c..282d875e 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -37,40 +37,27 @@ from .match import Matcher import ast +# Main Page of mentor application class IndexView(views.generic.ListView): - """ - Define homepage view, inheriting ListView class, which specifies a context variable. - - Note that login is required to view the items on the page. - """ - template_name = 'mentors/index.html' def get_context_data(self, **kwargs): ctx = super(IndexView, self).get_context_data(**kwargs) - # check if there exist a mentor application - ctx['applied'] = Mentor.applied + ctx['applied'] = self.request.user.userprofile.mentor_applied + print(ctx['applied']) return ctx def get_queryset(self): - """Override function in parent class and return all questions.""" - return Mentor.objects.all() +def viewindex(request): + return render(request, 'mentors/index.html', {'applied': request.user.userprofile.mentor_applied}) + class ApplyView(views.generic.ListView): template_name = 'mentors/apply.html' def get_context_data(self, **kwargs): ctx = super(ApplyView, self).get_context_data(**kwargs) - # check if there exist a mentor application - ctx['applied'] = Mentor.applied - ctx['applications'] = Mentor.objects.all() - ctx['step'] = Mentor.step - - if (Mentor.applied): - ctx['step'] = Mentor.step - else: - ctx['step'] = 1 - Mentor.step = 1 + ctx['applied'] = self.request.user.userprofile.mentor_applied return ctx def get_queryset(self): @@ -80,9 +67,6 @@ class view_applyView(views.generic.ListView): template_name = 'mentors/view_application.html' def get_context_data(self, **kwargs): ctx = super(view_applyView, self).get_context_data(**kwargs) - # check if there exist a mentor application - ctx['applied'] = Mentor.applied - ctx['applications'] = Mentor.objects.all() return ctx def get_queryset(self): @@ -150,37 +134,44 @@ def get_context_data(self, **kwargs): def get_queryset(self): return Course.objects.all() -def step1(request): - initial={'fn': request.session.get('fn', None)} - form = PersonForm(request.POST or None, initial=initial) - if request.method == 'POST': - if form.is_valid(): - request.session['fn'] = form.cleaned_data['fn'] - return HttpResponseRedirect(reverse('step2')) - return render(request, 'step1.html', {'form': form}) # apply step def applystep(request): - initial={'RIN': request.session.get('RIN', None), - 'first_name': request.session.get('first_name', None), - 'last_name': request.session.get('last_name', None), - 'GPA': request.session.get('GPA', None), - 'email': request.user.email, - 'phone': request.session.get('phone', None), - 'recommender': request.session.get('recommender', None)} + this_user = request.user.userprofile + p = this_user.mentor_profile + + initial={ + 'RIN': request.session.get('RIN', p.RIN if this_user.mentor_applied else None), + 'first_name': request.session.get('first_name', p.first_name if this_user.mentor_applied else None), + 'last_name': request.session.get('last_name', p.last_name if this_user.mentor_applied else None), + 'GPA': request.session.get('GPA', p.GPA if this_user.mentor_applied else None), + 'email': request.user.email, + 'phone': request.session.get('phone', p.phone if this_user.mentor_applied else None), + 'recommender': request.session.get('recommender', p.recommender if this_user.mentor_applied else None) + } + + print(request.user.userprofile.time_creation) form = MentorApplicationfoForm_step1(request.POST or None, initial=initial) if request.method == 'POST': # initate a new mentor applicant if form.is_valid(): - #new_applicant = form.save() - #order_str = breakties(request.POST['pref_order']) - request.session['RIN'] = form.cleaned_data['RIN'] - request.session['first_name'] = form.cleaned_data['first_name'] - request.session['last_name'] = form.cleaned_data['last_name'] - request.session['GPA'] = form.cleaned_data['GPA'] - request.session['email'] = request.user.email, - request.session['phone'] = form.cleaned_data['phone'] - request.session['recommender'] = form.cleaned_data['recommender'] + if (this_user.mentor_applied): + p.RIN = form.cleaned_data['RIN'] + p.first_name = form.cleaned_data['first_name'] + p.last_name = form.cleaned_data['last_name'] + p.GPA = form.cleaned_data['GPA'] + p.email = request.user.email, + p.phone = form.cleaned_data['phone'] + p.recommender = form.cleaned_data['recommender'] + p.save() + else: + request.session['RIN'] = form.cleaned_data['RIN'] + request.session['first_name'] = form.cleaned_data['first_name'] + request.session['last_name'] = form.cleaned_data['last_name'] + request.session['GPA'] = form.cleaned_data['GPA'] + request.session['email'] = request.user.email, + request.session['phone'] = form.cleaned_data['phone'] + request.session['recommender'] = form.cleaned_data['recommender'] ''' pref = new_applicant.course_pref pref[new_applicant.RIN] = order_str @@ -189,7 +180,6 @@ def applystep(request): l = pref[new_applicant.RIN] l = [n.strip() for n in ast.literal_eval(l)] # convert str list to actual list u['a', 'b', 'c'] -> ['a', 'b', 'c'] ''' - print('yes') return HttpResponseRedirect(reverse('mentors:applystep2')) #return render(request, 'mentors/index.html', {'applied': True}) @@ -203,35 +193,80 @@ def applystep(request): # Compensation agreement def applystep2(request): - initial={'compensation': request.session.get('compensation', None)} + this_user = request.user.userprofile + p = this_user.mentor_profile + initial={ + 'compensation': request.session.get('compensation', p.compensation if this_user.mentor_applied else None), + 'studnet_status':request.session.get('studnet_status', p.studnet_status if this_user.mentor_applied else None), + 'employed_paid_before':request.session.get('employed_paid_before', p.employed_paid_before if this_user.mentor_applied else None) + } + form = MentorApplicationfoForm_step2(request.POST or None, initial=initial) if request.method == 'POST': - if form.is_valid(): - request.session['compensation'] = form.cleaned_data['compensation'] + if form.is_valid(): + if (this_user.mentor_applied): + p.compensation = form.cleaned_data['compensation'] + p.studnet_status = form.cleaned_data['studnet_status'] + p.employed_paid_before = form.cleaned_data['employed_paid_before'] + p.save() + else: + request.session['compensation'] = form.cleaned_data['compensation'] + request.session['studnet_status'] = form.cleaned_data['studnet_status'] + request.session['employed_paid_before'] = form.cleaned_data['employed_paid_before'] + return HttpResponseRedirect(reverse('mentors:applystep3')) else: print(form.errors) + return render(request, 'mentors/apply.html', {'apply_form': form}) # Course grade and mentor experience def applystep3(request): + this_user = request.user.userprofile + p = this_user.mentor_profile + initial={} - for course in Course.objects.all(): - course_grade = course.name + "_grade" - course_exp = course.name + "_exp" - initial.update({course_grade: request.session.get(course_grade, 'n')}) - initial.update({course_exp: request.session.get(course_exp, 'N')}) + if (this_user.mentor_applied): + for course in Course.objects.all(): + this_grade = Grade.objects.filter(course = course, student = p).first() + course_grade = course.name + "_grade" + course_exp = course.name + "_exp" + initial.update({course_grade: request.session.get(course_grade, this_grade.student_grade)}) + initial.update({course_exp: request.session.get(course_exp, 'N' if this_grade.mentor_exp == False else 'Y')}) + else: + for course in Course.objects.all(): + course_grade = course.name + "_grade" + course_exp = course.name + "_exp" + initial.update({course_grade: request.session.get(course_grade, 'n')}) + initial.update({course_exp: request.session.get(course_exp, 'N')}) + + form = MentorApplicationfoForm_step3(request.POST or None, initial=initial) if request.method == 'POST': if form.is_valid(): - for course in Course.objects.all(): - course_grade = course.name + "_grade" - course_exp = course.name + "_exp" - request.session[course_grade] = form.cleaned_data[course_grade] - request.session[course_exp] = form.cleaned_data[course_exp] + if (this_user.mentor_applied): + for course in Course.objects.all(): + course_grade = course.name + "_grade" + course_exp = course.name + "_exp" + this_grade = Grade.objects.filter(course = course, student = p).first() + this_grade.student_grade = form.cleaned_data[course_grade] # Grade on this course + this_grade.mentor_exp = True if form.cleaned_data[course_exp] == 'Y' else False + this_grade.save() + # if the student has failed the class, or is progressing, we consider he did not take the course + if (this_grade.student_grade != 'p' and this_grade.student_grade != 'n' and this_grade.student_grade != 'f'): + this_grade.have_taken = True + else: + this_grade.have_taken = False + this_grade.save() + else: + for course in Course.objects.all(): + course_grade = course.name + "_grade" + course_exp = course.name + "_exp" + request.session[course_grade] = form.cleaned_data[course_grade] + request.session[course_exp] = form.cleaned_data[course_exp] return HttpResponseRedirect(reverse('mentors:applystep4')) else: print(form.errors) @@ -240,51 +275,88 @@ def applystep3(request): # Course preference def applystep4(request): - form = MentorApplicationfoForm_step4(request.POST or None, initial={}) + this_user = request.user.userprofile + p = this_user.mentor_profile + + initial={ 'pref_order': request.session.get('pref_order', p.course_pref if this_user.mentor_applied else None),} + if (this_user.mentor_applied): + prefer_list = ast.literal_eval(request.session.get('pref_order', (p.course_pref))) + print(len(prefer_list)) + print((prefer_list)) + + else: + prefer_list = ast.literal_eval(request.session.get('pref_order', '[]')) + + course_list = [c.name for c in Course.objects.all()] + pref_courses = Course.objects.filter(name__in=prefer_list) + not_pref_courses = Course.objects.filter(name__in=[item for item in course_list if item not in prefer_list]) + + #print([i.name for i in pref_courses]) + #print([i.name for i in not_pref_courses]) + + #pref_course = Course.objects.filter(name in set(pref_order)) + form = MentorApplicationfoForm_step4(request.POST or None, initial = initial) if request.method == 'POST': - if form.is_valid(): - request.session['pref_order'] = form.cleaned_data['pref_order'] - print(form.cleaned_data['pref_order']) + if form.is_valid(): + + if (this_user.mentor_applied): + p.pref_order = (form.cleaned_data['pref_order']) + p.save() + request.session['pref_order'] = (form.cleaned_data['pref_order']) + print(request.session['pref_order']) + #print(breakties(form.cleaned_data['pref_order'])) return HttpResponseRedirect(reverse('mentors:applystep5')) else: print(form.errors) - return render(request, 'mentors/apply.html', {'courses':Course.objects.all(), 'apply_form': form}) + return render(request, 'mentors/apply.html', {'pref_courses': pref_courses, 'not_pref_courses': not_pref_courses, 'courses':Course.objects.all(), 'apply_form': form}) # Time slots page def applystep5(request): + this_user = request.user.userprofile + p = this_user.mentor_profile initial={ - 'time_slots': request.session.get('time_slots', None), - 'other_times': request.session.get('other_times', None), + 'time_slots': request.session.get('time_slots', p.time_slots if this_user.mentor_applied else None), + 'other_times': request.session.get('other_times', p.other_times if this_user.mentor_applied else None), } - + form = MentorApplicationfoForm_step5(request.POST or None, initial=initial) if request.method == 'POST': if form.is_valid(): #order_str = breakties(request.POST['pref_order']) - request.session['time_slots'] = form.cleaned_data['time_slots'] - request.session['other_times'] = form.cleaned_data['other_times'] + if (this_user.mentor_applied): + p.time_slots = form.cleaned_data['time_slots'] + p.other_times = form.cleaned_data['other_times'] + p.save() + else: + request.session['time_slots'] = form.cleaned_data['time_slots'] + request.session['other_times'] = form.cleaned_data['other_times'] return HttpResponseRedirect(reverse('mentors:applystep6')) else: print(form.errors) return render(request, 'mentors/apply.html', {'courses':Course.objects.all(), 'apply_form': form}) + # Students Additional Page def applystep6(request): - initial={ - 'relevant_info': request.session.get('relevant_info', None), - } + this_user = request.user.userprofile + p = this_user.mentor_profile + initial={ 'relevant_info': request.session.get('relevant_info', p.relevant_info if this_user.mentor_applied else None),} + form = MentorApplicationfoForm_step6(request.POST or None, initial=initial) if request.method == 'POST': if form.is_valid(): - #order_str = breakties(request.POST['pref_order']) - request.session['relevant_info'] = form.cleaned_data['relevant_info'] - submit_application(request) - return render(request, 'mentors/index.html',{}) + if (this_user.mentor_applied): + p.relevant_info = form.cleaned_data['relevant_info'] + p.save() + else: + request.session['relevant_info'] = form.cleaned_data['relevant_info'] + submit_application(request) # Sumbit a new application + return HttpResponseRedirect(reverse('mentors:index')) else: print(form.errors) - return render(request, 'mentors/apply.html', {'courses':Course.objects.all(), 'apply_form': form}) + return render(request, 'mentors/apply.html', {'apply_form': form}) # return the prefer after brutally break ties @@ -299,13 +371,18 @@ def breakties(order_str): return l def submit_application(request): + new_applicant = Mentor() new_applicant.RIN = request.session["RIN"] new_applicant.first_name = request.session["first_name"] new_applicant.last_name = request.session["last_name"] new_applicant.GPA = request.session["GPA"] new_applicant.phone = request.session["phone"] + new_applicant.recommender = request.session["recommender"] + new_applicant.compensation = request.session["compensation"] + new_applicant.studnet_status = request.session["studnet_status"] + new_applicant.employed_paid_before = request.session["employed_paid_before"] # create a dictionary to store a list of preference #rin = new_applicant.RIN @@ -314,13 +391,20 @@ def submit_application(request): #pref[rin] = breakties(request.session["pref_order"]) #pref.save() - new_applicant.course_pref = breakties(request.session["pref_order"]) + new_applicant.course_pref = (request.session["pref_order"]) new_applicant.time_slots = request.session["time_slots"] new_applicant.other_times = request.session["other_times"] new_applicant.relevant_info = request.session["relevant_info"] - new_applicant.save() - for i in new_applicant.time_slots: + + # Save the new application to the profile + this_user = request.user.userprofile + this_user.mentor_applied = True + this_user.mentor_profile = new_applicant + this_user.save() + + + for i in this_user.mentor_profile.course_pref: print(i) #orderStr = self.cleaned_data["pref_order"] @@ -346,17 +430,24 @@ def submit_application(request): else: new_grade.have_taken = False new_grade.save() - print(new_grade.course.name + ": " + new_grade.student_grade.upper()) - + print(new_grade.course.name + ": " + new_grade.student_grade) return new_applicant # withdraw application, should add semester later def withdraw(request): if request.method == 'GET': - Mentor.objects.all().delete() - Mentor.applied = False - return HttpResponseRedirect(reverse('mentors:index')) + try: + #request.user.userprofile.mentor_profile.delete() + request.user.userprofile.mentor_applied = False + print(request.user.userprofile.mentor_applied) + request.user.userprofile.save() + request.user.save() + except: + print('Can not delete mentor application') + # Clear sessions + # request.session.flush() + return render(request, 'mentors/index.html', {'applied': False}) # load CS_Course.csv def addcourse(request): @@ -413,7 +504,6 @@ def addStudentRandom(request): numClass = len(Course.objects.all()) if request.method == 'POST': - #Mentor.objects.all().delete() num_students = request.POST['num_students'] for i in range(int(num_students)): @@ -437,7 +527,7 @@ def addStudentRandom(request): for course in Course.objects.all(): new_grade = Grade(id=None) #glist = ['a','a-','b+','b','b-','c+','c','c-','d+','d','f','p','n'] - glist = ['a','a-','b+','b','b-','c','c+','n'] + glist = ['a','a-','b+','b','b-','c','c+','n'] new_grade.student_grade = random.choice(glist) if (new_grade.student_grade != 'p' and new_grade.student_grade != 'n' and new_grade.student_grade != 'f'): @@ -462,7 +552,7 @@ def StartMatch(request): 'b+': 3.33, 'b': 3, 'b-': 2.67, 'c+': 2.33, 'c': 2, 'c-': 1.67, 'd+': 1.33, 'd': 1, 'f': 0, - 'p': 0, 'n': 0} + 'p': 0, 'n': 0, 'ap': 4,} # begin matching: studentFeatures = {} From 01f1edece812151a271d894ced75a6e1d0b079b5 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 23 Nov 2019 10:54:02 -0500 Subject: [PATCH 27/44] Link mentor app to each account --- compsocsite/static/css/shared.css | 45 +++- compsocsite/static/css/submission_form.css | 67 ++++-- compsocsite/static/js/course.js | 235 +++++++++++---------- 3 files changed, 212 insertions(+), 135 deletions(-) diff --git a/compsocsite/static/css/shared.css b/compsocsite/static/css/shared.css index 698e45b7..fc2413db 100755 --- a/compsocsite/static/css/shared.css +++ b/compsocsite/static/css/shared.css @@ -122,7 +122,7 @@ button:hover .greencolor { .margin-panel-top { margin-top: 12px; } - + @media only screen and (max-device-width: 480px) { .panel-body { padding-left: 1px; @@ -205,6 +205,10 @@ ul.example li.placeholder:before { /** Define arrowhead **/ } +panel-round-box{ + border-radius: 4rem; + color: rgb(88, 54, 54); +} /* image */ img.item { @@ -371,11 +375,14 @@ th { border-radius: 0.3rem; } + +.list-element.ui-selecting { + background-color: rgb(109, 108, 106) +} .list-element.ui-selected { background-color: rgb(176, 197, 214) } - .choice1 li>div { display: inline-block; position: relative; @@ -384,18 +391,26 @@ th { display: inline-block; } -.choice1 li .div-sortablehandle { +.ul-list li>div { + display: inline-block; + position: relative; + width: 100%; + height: 100%; + display: inline-block; +} + +.choice1 li .li-sortablehandle { display: none; opacity: 0.5; - left: -100%; + top: -42px; } -.choice1 li.ui-selected .div-sortablehandle { +.choice1 li.ui-selected .li-sortablehandle { display: inline-block; cursor: default; - width: 100px; - height: 28px; - left: -107%; + width: 125px; + height: 35px; + top: -42px; border-radius: 0.1875rem; } @@ -414,15 +429,15 @@ th { cursor: default; width: 125px; height: 35px; - background-color: rgb(193, 214, 231); + background-color: rgb(157, 171, 182); border-radius: 0.3rem; } .col-placeHolder { white-space: nowrap; - background-color: rgb(193, 214, 231); height: 40px; width: 550px; + background-color: rgb(157, 171, 182); padding: 0; margin: 10px 0px; border-radius: 0.3rem; @@ -439,6 +454,14 @@ th { border: 0px solid darkgray; } - +.noselect { + cursor: default; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} diff --git a/compsocsite/static/css/submission_form.css b/compsocsite/static/css/submission_form.css index 63c84835..cc9ff4ae 100755 --- a/compsocsite/static/css/submission_form.css +++ b/compsocsite/static/css/submission_form.css @@ -33,15 +33,48 @@ label.radio.inline{ float:left; color:red; } +.c_tier { + float: left; + + color: dimgray; + height: 40px; + width: 40px; + text-align: center; + vertical-align: middle; + line-height: 40px; + + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 151px; + font-weight: 900; + font-weight: bold; + cursor: pointer; + margin: 0px 15px 0px 0px; + border-radius: 0.3rem; +} + .course_choice { white-space: nowrap; - background:rgb(223, 222, 222); + background:whitesmoke; height: 40px; line-height: 40px; - width: 650px; + width: 100%; padding: 0; - margin: 6px 0px; - border: 0; + border-top: 2px solid rgb(187, 179, 179); + border-radius: 0.1rem; + flex-wrap: wrap; +} + + +.course_choice2 { + white-space: nowrap; + background:whitesmoke; + height: 40px; + line-height: 40px; + width: 100%; + padding: 0; + border-top: 2px solid rgb(187, 179, 179); border-radius: 0.1rem; flex-wrap: wrap; } @@ -52,16 +85,28 @@ label.radio.inline{ float: left; margin: 2.5px 4px; cursor: default; - width: 175px; + width: 275px; height: 35px; line-height: 35px; - background-color: white; - text-align: center; - font-size: 12px; - color: #252525; - border: 1px solid rgb(145, 138, 138); + font-size: 15px; + color:black; + border-radius: 0.2rem; +} + +.course-element-still { + flex: 0 50%; + display: inline-block; + float: left; + margin: 2.5px 4px; + cursor: default; + width: 275px; + height: 35px; + line-height: 35px; + font-size: 15px; + color:black; border-radius: 0.2rem; } + .course-element.ui-selecting { background-color: rgb(109, 108, 106) } @@ -130,5 +175,3 @@ label.radio.inline{ padding-bottom: 11px; border-top: 1px solid #d5dfe5; } - - diff --git a/compsocsite/static/js/course.js b/compsocsite/static/js/course.js index bc5e1f1c..057ac8d5 100644 --- a/compsocsite/static/js/course.js +++ b/compsocsite/static/js/course.js @@ -10,13 +10,13 @@ var order1 = ""; var order2 = ""; var flavor = ""; var startTime = 0; -var allowTies = true; +var allowTies = false; var commentTime = ""; -var method = 2; // 1 is twoCol, 2 is oneCol, 3 is Slider +var method = 1; // 1 is twoCol, 2 is oneCol, 3 is Slider var methodIndicator = "two_column"; var init_star = false; var animOffset = 200; // animation speed of ranking UIs, 200 = 0.2s -var top_tier_layer = 0; +var top_c_tier_layer = 0; function select(item){ var d = (Date.now() - startTime).toString(); @@ -107,7 +107,7 @@ function orderCol(num){ if( $( this ).children().size() > 0 ){ var inner = []; $( this ).children().each(function( index ){ - if(!$(this).hasClass("tier")) inner.push($( this ).attr('type')); + if(!$(this).hasClass("c_tier")) inner.push($( this ).attr('type')); }); order.push(inner); } @@ -198,7 +198,7 @@ function dictSlideStar(str){ for(i = 0; i < arr.length; i++){ var j; for(j = 0; j < arr[i].length; j++){ - arr[i][j]["tier"] = i+1; + arr[i][j]["c_tier"] = i+1; } } return arr; @@ -214,8 +214,8 @@ function dictYesNo(){ temp = {}; temp["name"] = $(value).attr("id"); temp["ranked"] = 0; - if($(value).children()[0].checked){temp["tier"] = 1; yes.push(temp); } - else{temp["tier"] = 2; no.push(temp); } + if($(value).children()[0].checked){temp["c_tier"] = 1; yes.push(temp); } + else{temp["c_tier"] = 2; no.push(temp); } } }); if(yes.length != 0){ order.push(yes); } @@ -238,8 +238,8 @@ function dictYesNo2(){ else if (i == 1){temp["position"] = "(1,2)";} else if (i == 2){temp["position"] = "(2,1)";} else{temp["position"] = "(2,2)";} - if($(value).children()[0].checked){temp["tier"] = 1; yes.push(temp); } - else{temp["tier"] = 2; no.push(temp); } + if($(value).children()[0].checked){temp["c_tier"] = 1; yes.push(temp); } + else{temp["c_tier"] = 2; no.push(temp); } i++; } }); @@ -255,22 +255,22 @@ function dictCol(num){ if(num == 1){ arr = [$('#left-sortable'), $('#right-sortable')]; } else if(num == 2){ arr = [$('#one-sortable')]; } var order = []; - var tier = 1; + var c_tier = 1; var item_type = ".course-element"; $.each(arr, function( index, value ){ value.children().each(function( i1 ){ - if( $( this ).children().size() > 0 && $( this ).attr("class") != "top_tier"){ + if( $( this ).children().size() > 0 && $( this ).attr("class") != "top_c_tier"){ var inner = []; $( this ).children().each(function( i2 ){ var temp = {}; temp["name"] = $(item_type + "[type='" + $( this ).attr('type') + "']").attr('id'); temp["utility"] = $(item_type + "[type='" + $( this ).attr('type') + "']").attr('title'); - temp["tier"] = tier; + temp["c_tier"] = c_tier; temp["ranked"] = index; inner.push(temp); }); order.push(inner); - tier++; + c_tier++; } }); }); @@ -279,18 +279,18 @@ function dictCol(num){ function twoColSort( order ){ var html = ""; - var tier = 1; + var c_tier = 1; var emptyLine = "
        "; html += emptyLine; $.each(order, function(index, value){ - html += "
          #" + tier + "
          "; + html += "
            #" + c_tier + "
            "; $.each(value, function(i, v){ html += "
          • "; html += $(".course-element[type='" + v.toString() + "']").html(); html += "
          • "; }); html += "
          "; - tier ++; + c_tier ++; }); html += emptyLine; $('#left-sortable').html(html); @@ -302,18 +302,18 @@ function twoColSort( order ){ function oneColSort( order ){ // var html = "
            "; var html = ""; - var tier = 1; + var c_tier = 1; var emptyLine = "
            "; html += emptyLine; $.each(order, function(index, value){ - html += "
              #" + tier + "
              "; + html += "
                #" + c_tier + "
                "; $.each(value, function(i, v){ html += "
              • "; html += $("#oneColSection .course-element[type='" + v.toString() + "']").html(); html += "
              • "; }); html += "
              "; - tier ++; + c_tier ++; }); //html += "
              "; @@ -403,51 +403,57 @@ function yesNoZeroSort( order ){ function changeCSS(){ // if method is twocol if(method == 1){ - $(".course_choice").css("width", "550px"); - $(".empty"). css("width", "550px"); - $(".col-placeHolder").css("width", "550px"); + $(".course_choice").css("width", "100%"); + $(".empty"). css("width", "100%"); + $(".col-placeHolder").css("width", "100%"); // extend the height of course_choice box if there exists more than 3 course-element in a row // vice versa $("#left-sortable").children(".course_choice").each(function(){ size = $(this).children(":not(.ui-selected, .transporter)").size(); //$(this).css("height", ((size-1)*40).toString() + "px"); - if(size > 4){ - size -= 1; - num = Math.ceil(size/3); - $(this).css("height", (num*40).toString() + "px"); - $(this).children(".tier").css( "height", (num*40).toString() + "px"); - $(this).children(".tier").css( "line-height", (num*40).toString() + "px"); - } - else{ - $(this).css("height", "40px"); - $(this).children(".tier").css( "height", "40px"); - $(this).children(".tier").css( "line-height", "40px"); - + if(allowTies){ + if(size > 4){ + size -= 1; + num = Math.ceil(size/3); + $(this).css("height", (num*40).toString() + "px"); + $(this).children(".c_tier").css( "height", (num*40).toString() + "px"); + $(this).children(".c_tier").css( "line-height", (num*40).toString() + "px"); + } + else{ + $(this).css("height", "40px"); + $(this).children(".c_tier").css( "height", "40px"); + $(this).children(".c_tier").css( "line-height", "40px"); + + } } + }); } // if method is onecol, extend the selection bar else if(method == 2){ - $(".course_choice").css("width", "800px"); - $(".empty"). css("width", "800px"); - $(".col-placeHolder").css("width", "800px"); + $(".course_choice").css("width", "100%"); + $(".empty"). css("width", "100%"); + $(".col-placeHolder").css("width", "100%"); } // extend the height of course_choice box if there exists more than 6 course-element in a row // vice versa $("#left-sortable").children(".course_choice").each(function(){ size = $(this).children(":not(.ui-selected, .transporter)").size(); + if(allowTies){ + if(size > 5){ size -= 1; num = Math.ceil(size/4); $(this).css("height", (num*40).toString() + "px"); - $(this).children(".tier").css( "height", (num*40).toString() + "px"); - $(this).children(".tier").css( "line-height", (num*40).toString() + "px"); + $(this).children(".c_tier").css( "height", (num*40).toString() + "px"); + $(this).children(".c_tier").css( "line-height", (num*40).toString() + "px"); } else{ $(this).css("height", "40px"); - $(this).children(".tier").css( "height", "40px"); - $(this).children(".tier").css( "line-height", "40px"); + $(this).children(".c_tier").css( "height", "40px"); + $(this).children(".c_tier").css( "line-height", "40px"); + } } }); @@ -509,17 +515,17 @@ var VoteUtil = (function () { temp_data["time"] = [d]; temp_data["rank"] = [dictCol(1)]; if (method == 1) { - var tier = $("#right-sortable").children().size()+1; + var c_tier = $("#right-sortable").children().size()+1; // move the left items over to the right side $("#left-sortable").children().each(function(index) { if ($(this).children().size() > 0) { $(this).children().each(function(index) { var temp = $("#right-sortable").html(); //$(this).attr("onclick")="VoteUtil.moveToPref(this)"; - if (!$(this).hasClass("tier")) { + if (!$(this).hasClass("c_tier")) { $("#right-sortable").html( - temp + "
                " + "
                #" + tier.toString() + "
                " + $(this)[0].outerHTML + "
              "); - tier++; + temp + "
                " + $(this)[0].outerHTML + "
              "); + c_tier++; } }); } @@ -554,7 +560,7 @@ var VoteUtil = (function () { var final_list; var item_type = ".course-element"; var record_data = {}; - $(".top_tier").remove(); + $(".top_c_tier").remove(); if(method == 1) {order_list = orderCol(0); final_list = dictCol(1);} else if(method == 2){ order_list = orderCol(method); final_list = dictCol(2);} else if(method == 3){ order_list = orderSlideStar('slide'); item_type = ".slider_item"; final_list = dictSlideStar('slide');} @@ -564,11 +570,11 @@ var VoteUtil = (function () { else{location.reload(); } var final_order = []; for (var i = 0; i < order_list.length; i++) { - var sametier = []; + var samec_tier = []; for (var j = 0; j < order_list[i].length; j++) { - sametier.push($(item_type + "[type='" + order_list[i][j].toString() + "']").attr('id')); + final_order.push( order_list[i][j].toString() ); } - final_order.push(sametier); + //final_order.push(samec_tier); } order = JSON.stringify(final_order); //var d = Date.now() - startTime; @@ -612,19 +618,22 @@ var VoteUtil = (function () { // moves preference item obj from the right side to the bottom of the left side function moveToPref(obj) { - $(obj).removeClass('choice2'); + $(obj).removeClass('course_choice2'); $(obj).addClass('course_choice'); var time = 100 var prefcolumn = $('#left-sortable'); + var tier = "
              0
              "; var currentli = $(obj); - var tier = currentli.children().first().attr("alt"); - var tierRight = 1; - var tierleft = 1; + var c_tier = 0; + var c_tierRight = 1; + var c_tierleft = 1; var item = currentli.children().first().attr("id"); var emptyLine = "
              "; - + var d = (Date.now() - startTime).toString(); + + currentli.prepend(tier) temp_data = { "item": item }; @@ -638,19 +647,19 @@ var VoteUtil = (function () { } else { $('#left-sortable').children(".course_choice").last().after(currentli); } - //record += d+ "::clickFrom::" + item + "::"+ tier+";;"; + //record += d+ "::clickFrom::" + item + "::"+ c_tier+";;"; - tier = currentli.children().first().attr("alt"); + c_tier = currentli.children().first().attr("alt"); if ($('#left-sortable').children().size() != 0) { enableSubmission(); } - $('#right-sortable').children(".choice2").each(function() { - $(this).children(".tier").text("#" + tierRight.toString()); - tierRight++; + $('#right-sortable').children(".course_choice2").each(function() { + $(this).children(".c_tier").text(c_tierRight.toString()); + c_tierRight++; }); $('#left-sortable').children(".course_choice").each(function() { - $(this).children(".tier").text("#" +tierleft.toString()); - tierleft++; + $(this).children(".c_tier").text(c_tierleft.toString()); + c_tierleft++; }); $('#left-sortable').children().each(function() { $(this).removeAttr('onclick'); @@ -663,20 +672,20 @@ var VoteUtil = (function () { temp.push(temp_data); record = JSON.stringify(temp); //d = Date.now() - startTime; - //record += d+ "::clickTo::" + item + "::"+ tier+";;;"; + //record += d+ "::clickTo::" + item + "::"+ c_tier+";;;"; /* if(methodIndicator == "two_column") { var d = (Date.now() - startTime).toString(); var temp = JSON.parse(record); - temp["two_column"].push({"method":methodIndicator,"time":d, "action":"click", "from":prev_tier,"to": tier, "item":item }); + temp["two_column"].push({"method":methodIndicator,"time":d, "action":"click", "from":prev_c_tier,"to": c_tier, "item":item }); record = JSON.stringify(temp); } else { var d = (Date.now() - startTime).toString(); var temp = JSON.parse(one_record); - temp["one_column"].push({"method":methodIndicator,"time":d, "action":"click", "from":prev_tier,"to": tier, "item":item }); + temp["one_column"].push({"method":methodIndicator,"time":d, "action":"click", "from":prev_c_tier,"to": c_tier, "item":item }); one_record = JSON.stringify(temp); } */ @@ -688,14 +697,16 @@ var VoteUtil = (function () { temp_data = {"item":""}; temp_data["time"] = [d]; temp_data["rank"] = [dictCol(1)]; - $('.choice2').each(function(){ - $(this).removeClass('choice2'); + $('.course_choice2').each(function(){ + $(this).removeClass('course_choice2'); $(this).addClass('course_choice'); }); emptyLine = "
              "; - if ($('#right-sortable').children().size() > 0 && $('#left-sortable').children().size()== 0) - {$('#left-sortable' ).html( emptyLine + $( '#left-sortable' ).html() + $( '#right-sortable' ).html() + emptyLine); - } + if ($('#right-sortable').children().size() > 0 && $('#left-sortable').children().size() == 0) { + $("#right-sortable").children(".course_choice").each(function(){ + moveToPref($(this)); + }); + } else if ($('#right-sortable').children().size() > 0 && $('#left-sortable').children().size() > 0){ $("#right-sortable").children(".course_choice").each(function(){ moveToPref($(this)); @@ -705,7 +716,7 @@ var VoteUtil = (function () { //VoteUtil.checkStyle(); enableSubmission(); $('.course_choice').each(function(){ - $(this).removeAttr('onclick'); + $(this).removeAttr('onclick'); }); //var d = Date.now() - startTime; //record += d + ";;;"; @@ -874,48 +885,16 @@ $( document ).ready(function() { if (allowTies){ str = ".course_choice, .sortable-ties"; $('.sortable-ties').selectable({ - cancel: '.tier', + cancel: '.c_tier', filter: "li", }); - } - else{ - str = ".sortable-ties"; - } - $('.sortable-ties').sortable({ - placeholder: "col-placeHolder", - handle: ".tier", - //items: "ul", - revert:'invalid', - start: function(e, ui){ - $(".col-placeHolder").hide(); - }, - change: function (e, ui){ - $(".col-placeHolder").hide().show(animOffset, function(){ - if (method == 1) { - $(".col-placeHolder").css("width", "550px"); - } - else if (method == 2) { - $(".col-placeHolder").css("width", "800px"); - } - $(".li-placeHolder").css({ "float": "none", "height": "40px", "margin": "0px 0px" }); - }); - changeCSS(); - }, - stop: function(e, ui) { - checkAll(); - removeSelected(); - resetEmpty(); - changeCSS(); - } - }); - - $('.course_choice').sortable({ - cancel: '.tier .empty', + $('.course_choice').sortable({ + cancel: '.c_tier .empty', cursorAt: { top: 20, left: 60 }, - items: "li:not(.tier)", + items: "li:not(.c_tier)", placeholder: "li-placeHolder", connectWith: str, start: function(e, ui){ @@ -940,10 +919,10 @@ $( document ).ready(function() { $(".li-placeHolder").hide().show(animOffset, function(){ if (ui.placeholder.parent().hasClass("sortable-ties")) { if (method == 1) { - $(".li-placeHolder").css("width", "550px"); + $(".li-placeHolder").css("width", "100%"); } else if (method == 2) { - $(".li-placeHolder").css("width", "800px"); + $(".li-placeHolder").css("width", "100%"); } $(".li-placeHolder").css({ "float": "none", "height": "40px", "margin": "0px 0px" }); } @@ -968,14 +947,46 @@ $( document ).ready(function() { }).disableSelection(); + } + else{ + str = ".sortable-ties"; + } + $('.sortable-ties').sortable({ + placeholder: "col-placeHolder", + //handle: ".c_tier", + //items: "ul", + start: function(e, ui){ + $(".col-placeHolder").hide(); + }, + change: function (e, ui){ + $(".col-placeHolder").hide().show(animOffset, function(){ + if (method == 1) { + $(".col-placeHolder").css("width", "100%"); + } + else if (method == 2) { + $(".col-placeHolder").css("width", "100%"); + } + $(".li-placeHolder").css({ "float": "none", "height": "40px", "margin": "0px 0px" }); + }); + changeCSS(); + }, + stop: function(e, ui) { + checkAll(); + removeSelected(); + resetEmpty(); + changeCSS(); + } + }); + + } function checkAll() { t1 = 1; t2 = 1; list = []; html = "
                "; - if(method == 1){ html += "
                0
                "; } - if(method == 2){ html += "
                0
                "; } + if(method == 1){ html += "
                0
                "; } + if(method == 2){ html += "
                0
                "; } $('.sortable-ties').children().each(function() { if ($(this).hasClass('course-element')) { @@ -1007,8 +1018,8 @@ $( document ).ready(function() { $(this).remove(); }); - $('.tier').each(function() { - $(this).text("\#"+t1.toString()); + $('.c_tier').each(function() { + $(this).text(t1.toString()); t1++; }); From 2867b1b862f811fb09a48e073ee3218a4198caf3 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 23 Nov 2019 11:12:49 -0500 Subject: [PATCH 28/44] Reverse Changes --- compsocsite/appauth/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compsocsite/appauth/views.py b/compsocsite/appauth/views.py index 49885f5e..d6d7027a 100755 --- a/compsocsite/appauth/views.py +++ b/compsocsite/appauth/views.py @@ -52,7 +52,7 @@ def register(request): # Update our variable to tell the template registration was successful. registered = True - htmlstr = "

                Click This Link To Activate Your Account

                " + htmlstr = "

                Click This Link To Activate Your Account

                " mail.send_mail("OPRA Confirmation","Please confirm your account registration.",'oprahprogramtest@gmail.com',[user.email],html_message=htmlstr) #else print (user_form.errors) else: @@ -99,7 +99,7 @@ def quickRegister(request, question_id): # Update our variable to tell the template registration was successful. registered = True - htmlstr = "

                Click This Link To Activate Your Account

                " + htmlstr = "

                Click This Link To Activate Your Account

                " mail.send_mail("OPRA Confirmation","Please confirm your account registration.",'oprahprogramtest@gmail.com',[user.email],html_message=htmlstr) #else print (user_form.errors) else: @@ -141,7 +141,7 @@ def user_login(request): else: email = user.email if email: - htmlstr = "Please CLICK HERE to activate your account." + htmlstr = "Please CLICK HERE to activate your account." mail.send_mail("OPRA Confirmation From Invalid Login","Please confirm your account registration.",'oprahprogramtest@gmail.com',[email],html_message=htmlstr) return HttpResponse("Your account is not active. We have resent an activation link to your email address. Please check.") else: @@ -249,7 +249,7 @@ def forgetPassword(request): if email == "" or username == "": return HttpResponseRedirect(request.META.get('HTTP_REFERER')) user = get_object_or_404(User, email=email, username=username) - htmlstr = "

                Click This Link To Reset Password

                " + htmlstr = "

                Click This Link To Reset Password

                " mail.send_mail("OPRA Forget Password","Please click the following link to reset password.",'oprahprogramtest@gmail.com',[email],html_message=htmlstr) return HttpResponse("An email has been sent to your email account. Please click on the link in that email and reset your password.") From 5bf17c3fa0a87c07e6720b1adc40dd641f2bc833 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 23 Nov 2019 15:28:23 -0500 Subject: [PATCH 29/44] Visual Changes --- compsocsite/mentors/forms.py | 45 +++++++++++----------- compsocsite/static/css/submission_form.css | 10 ++++- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index bbfbefa0..bedf8d89 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -69,9 +69,10 @@ def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step2, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.layout = Layout( - HTML('== COMPENSATION AND RESPONSIBILITIES =='), + HTML('''

                COMPENSATION AND RESPONSIBILITIES

                '''), + HTML("""
                """), + HTML("
                "), - HTML("""
                We currently have funding for a fixed number of paid programming mentors. Therefore, if you apply for pay, you may be asked to work for credit instead. Course credit counts as a graded 1-credit or 2-credit free elective. In general, if you work an average of 1-3 hours per week, you will receive 1 credit; if you average 4 or more hours per week, you will receive 2 credits. Note that no more than 4 credits may be earned as an undergraduate (spanning all courses and all semesters). Please do not apply for credit if you have already earned 4 credits as a mentor; apply only for pay. @@ -121,24 +122,19 @@ def __init__(self, *args, **kwargs): self.helper = FormHelper() courses = Course.objects.all() course_layout = Field() - for course in courses: - course_layout.append( HTML('''
                ''' + str(course)+ "
                ") ) - course_layout.append(HTML("       ")) + for course in courses: + course_layout.append(HTML('''
                ''')) + course_layout.append(HTML('''
                ''' + str(course.name)+ "
                ") ) + course_layout.append(HTML('''
                ''' + str(course.subject+" "+course.number)+ "
                ") ) + + course_layout.append(HTML("     ")) + course_layout.append(HTML('''
                ''')) grades = ( - ('a', 'A'), - ('a-', 'A-'), - ('b+', 'B+'), - ('b', 'B'), - ('b-', 'B-'), - ('c+', 'C+'), - ('c', 'C'), - ('c-', 'C-'), - ('d+', 'D+'), - ('d', 'D'), - ('f', 'F'), - ('p', 'Progressing'), - ('ap', 'AP'), - ('n', 'Not Taken'), + ('a', 'A'), ('a-', 'A-'), + ('b+', 'B+'), ('b', 'B'), ('b-', 'B-'), + ('c+', 'C+'), ('c', 'C'), ('c-', 'C-'), + ('d+', 'D+'), ('d', 'D'), ('f', 'F'), + ('p', 'Progressing'), ('n', 'Not Taken'), ) choices_YN = ( ('Y', 'YES'), @@ -149,14 +145,17 @@ def __init__(self, *args, **kwargs): label = "Grade on this course:", ) #self.initial[course.name+ "_grade"] = 'n' - self.fields[course.name+ "_exp"] = forms.ChoiceField(choices = choices_YN, label = "") #self.initial[course.name+ "_exp"] = 'N' - course_layout.append(HTML("""""")) + course_layout.append(HTML('''
                ''')) + + course_layout.append(HTML("""

                I have taken this course at RPI before and earned grade:  

                """)) course_layout.append(Field(course.name+ "_grade", label_class="float: left;")) - #course_layout.append(HTML("""
                """)) - course_layout.append(HTML("""""")) + course_layout.append(HTML("""
                """)) + course_layout.append(HTML("""

                I have mentored this RPI course before:  

                """)) course_layout.append(Field(course.name+ "_exp")) + course_layout.append(HTML('''
                ''')) + course_layout.append(HTML("""
                """)), diff --git a/compsocsite/static/css/submission_form.css b/compsocsite/static/css/submission_form.css index cc9ff4ae..2de878fd 100755 --- a/compsocsite/static/css/submission_form.css +++ b/compsocsite/static/css/submission_form.css @@ -17,11 +17,17 @@ label.radio.inline{ padding: 0px 15px; } +.form_title{ + font: 200px; + font-weight: bold; + float: left; +} + .course_title{ - font: 80px; - color: red; + font: 100px; font-weight: bold; float: left; + text-decoration: underline; } .course_block{ From c4ff16a05a71c52baff4e0eba39f187a6abfb9f5 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 23 Nov 2019 17:18:07 -0500 Subject: [PATCH 30/44] change cache --- compsocsite/mentors/CS_Course.csv | 24 +++--- compsocsite/mentors/forms.py | 4 +- .../mentors/templates/mentors/index.html | 10 ++- compsocsite/mentors/views.py | 75 +++++++------------ 4 files changed, 46 insertions(+), 67 deletions(-) diff --git a/compsocsite/mentors/CS_Course.csv b/compsocsite/mentors/CS_Course.csv index f7c23fb0..44ea7607 100644 --- a/compsocsite/mentors/CS_Course.csv +++ b/compsocsite/mentors/CS_Course.csv @@ -1,14 +1,10 @@ -Intro to Computer Programming,CSCI,1010,P_1 -Computer Science I,CSCI,1100,P_2 -Beginning Prog for Engineers,CSCI,1190,P_3 -Data Structures,CSCI,1200,P_4 -Foundations of Computer Science,CSCI,2200,P_5 -Intro to Algorithms,CSCI,2300,P_6 -Computer Organization,CSCI,2500,P_7 -Principles of Software,CSCI,2600,P_8 -Machine Learning from Data,CSCI,4100,P_3 -Network Programming,CSCI,4220,P_10 -Crypto and Network Security I,CSCI,4230,P_11 -Database Systems,CSCI,4380,P_2 -Programming Languages,CSCI,4430,P_5 -Large-Scale Programming and Testing,CSCI,4460,P_4 \ No newline at end of file +Computer Science I,CSCI,1100,P_1 +Beginning Prog for Engineers,CSCI,1190,P_2 +Data Structures,CSCI,1200,P_3 +Foundations of Computer Science,CSCI,2200,P_4 +Intro to Algorithms,CSCI,2300,P_5 +Computer Organization,CSCI,2500,P_6 +Principles of Software,CSCI,2600,P_7 +Intro to AI,CSCI,4150,P_8 +Operation Systems,CSCI,4210,P_9 +Database Systems,CSCI,4380,P_10 \ No newline at end of file diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index bedf8d89..22d6efb9 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -79,8 +79,9 @@ def __init__(self, *args, **kwargs):
                """), HTML("""
                """), + HTML("""
                """), - HTML(""""""), + HTML(""""""), Div('compensation', css_class = "inputline"), HTML("""
                """), @@ -91,6 +92,7 @@ def __init__(self, *args, **kwargs): HTML(""""""), Div('studnet_status', css_class = "inputline"), HTML("""
                """), + HTML("""
                """), HTML("""
                """), HTML("""
                For a paid position, you must follow the given instructions to ensure you are paid; otherwise, diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index 0b505980..4d7c4890 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -56,15 +56,19 @@
                {% if applied %} -

                Your mentor application success!

                -

                You can change your application at anytime, but remember, we will start to look at all the applications after 12/6, so make sure you submitted before the deadline.

                +

                Your mentor application is successful!

                +

                You can change your application at anytime, but remember, we will start to look at all the applications after 12/6, so make sure you filled out the form and submit before the deadline.

                View your application and make changes:   View and Change View +
                + +

                You can withdraw your application if you do not want to apply for mentor anymore. A new application can be started after you withdrawed.

                + - + {% else %} Begin to apply:   diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 282d875e..aadb6666 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -141,13 +141,13 @@ def applystep(request): p = this_user.mentor_profile initial={ - 'RIN': request.session.get('RIN', p.RIN if this_user.mentor_applied else None), - 'first_name': request.session.get('first_name', p.first_name if this_user.mentor_applied else None), - 'last_name': request.session.get('last_name', p.last_name if this_user.mentor_applied else None), - 'GPA': request.session.get('GPA', p.GPA if this_user.mentor_applied else None), + 'RIN': p.RIN if this_user.mentor_applied else request.session.get('RIN', None), + 'first_name': p.first_name if this_user.mentor_applied else request.session.get('first_name', None), + 'last_name': p.last_name if this_user.mentor_applied else request.session.get('last_name', None), + 'GPA': p.GPA if this_user.mentor_applied else request.session.get('GPA', None), 'email': request.user.email, - 'phone': request.session.get('phone', p.phone if this_user.mentor_applied else None), - 'recommender': request.session.get('recommender', p.recommender if this_user.mentor_applied else None) + 'phone': p.phone if this_user.mentor_applied else request.session.get('phone', None), + 'recommender': p.recommender if this_user.mentor_applied else request.session.get('recommender', None) } print(request.user.userprofile.time_creation) @@ -196,9 +196,9 @@ def applystep2(request): this_user = request.user.userprofile p = this_user.mentor_profile initial={ - 'compensation': request.session.get('compensation', p.compensation if this_user.mentor_applied else None), - 'studnet_status':request.session.get('studnet_status', p.studnet_status if this_user.mentor_applied else None), - 'employed_paid_before':request.session.get('employed_paid_before', p.employed_paid_before if this_user.mentor_applied else None) + 'compensation': p.compensation if this_user.mentor_applied else request.session.get('compensation', None), + 'studnet_status': p.studnet_status if this_user.mentor_applied else request.session.get('studnet_status', None), + 'employed_paid_before': p.employed_paid_before if this_user.mentor_applied else request.session.get('employed_paid_before', None) } form = MentorApplicationfoForm_step2(request.POST or None, initial=initial) @@ -233,8 +233,8 @@ def applystep3(request): this_grade = Grade.objects.filter(course = course, student = p).first() course_grade = course.name + "_grade" course_exp = course.name + "_exp" - initial.update({course_grade: request.session.get(course_grade, this_grade.student_grade)}) - initial.update({course_exp: request.session.get(course_exp, 'N' if this_grade.mentor_exp == False else 'Y')}) + initial.update({course_grade: this_grade.student_grade}) + initial.update({course_exp: 'N' if this_grade.mentor_exp == False else 'Y'}) else: for course in Course.objects.all(): course_grade = course.name + "_grade" @@ -280,7 +280,7 @@ def applystep4(request): initial={ 'pref_order': request.session.get('pref_order', p.course_pref if this_user.mentor_applied else None),} if (this_user.mentor_applied): - prefer_list = ast.literal_eval(request.session.get('pref_order', (p.course_pref))) + prefer_list = ast.literal_eval(p.course_pref) print(len(prefer_list)) print((prefer_list)) @@ -300,9 +300,10 @@ def applystep4(request): if form.is_valid(): if (this_user.mentor_applied): - p.pref_order = (form.cleaned_data['pref_order']) + p.course_pref = form.cleaned_data['pref_order'] p.save() - request.session['pref_order'] = (form.cleaned_data['pref_order']) + else: + request.session['pref_order'] = (form.cleaned_data['pref_order']) print(request.session['pref_order']) #print(breakties(form.cleaned_data['pref_order'])) return HttpResponseRedirect(reverse('mentors:applystep5')) @@ -316,8 +317,8 @@ def applystep5(request): this_user = request.user.userprofile p = this_user.mentor_profile initial={ - 'time_slots': request.session.get('time_slots', p.time_slots if this_user.mentor_applied else None), - 'other_times': request.session.get('other_times', p.other_times if this_user.mentor_applied else None), + 'time_slots': p.time_slots if this_user.mentor_applied else request.session.get('time_slots', None), + 'other_times': p.other_times if this_user.mentor_applied else request.session.get('other_times', None), } form = MentorApplicationfoForm_step5(request.POST or None, initial=initial) @@ -342,7 +343,7 @@ def applystep5(request): def applystep6(request): this_user = request.user.userprofile p = this_user.mentor_profile - initial={ 'relevant_info': request.session.get('relevant_info', p.relevant_info if this_user.mentor_applied else None),} + initial={ 'relevant_info': p.relevant_info if this_user.mentor_applied else request.session.get('relevant_info', None),} form = MentorApplicationfoForm_step6(request.POST or None, initial=initial) if request.method == 'POST': @@ -371,8 +372,8 @@ def breakties(order_str): return l def submit_application(request): - new_applicant = Mentor() + new_applicant.RIN = request.session["RIN"] new_applicant.first_name = request.session["first_name"] new_applicant.last_name = request.session["last_name"] @@ -383,14 +384,7 @@ def submit_application(request): new_applicant.compensation = request.session["compensation"] new_applicant.studnet_status = request.session["studnet_status"] new_applicant.employed_paid_before = request.session["employed_paid_before"] - - # create a dictionary to store a list of preference - #rin = new_applicant.RIN - #pref = Dict() - #pref.name = rin - #pref[rin] = breakties(request.session["pref_order"]) - #pref.save() - + new_applicant.course_pref = (request.session["pref_order"]) new_applicant.time_slots = request.session["time_slots"] new_applicant.other_times = request.session["other_times"] @@ -578,33 +572,19 @@ def StartMatch(request): classes = [c.name for c in Course.objects.all()] classCaps = {c.name: c.mentor_cap for c in Course.objects.all()} students = [s.RIN for s in Mentor.objects.all()] - numClasses = len(Course.objects.all()) studentPrefs = {s.RIN: [n.strip() for n in ast.literal_eval(s.course_pref)] for s in Mentor.objects.all()} - - - #studentPrefs = {s: [c for c in r.sample(classes, r.randint(numClasses, numClasses ))] for s in students} - #studentFeatures = {s: {c: tuple(r.randint(0, 10) for i in range(numFeatures)) for c in classRank} for s, classRank in studentPrefs.items()} classFeatures = {c.name: (c.feature_cumlative_GPA, c.feature_course_GPA, c.feature_has_taken, c.feature_mentor_exp) for c in Course.objects.all()} - #classFeatures = {c: (0, 1000, 5, 5) for c in classes} - matcher = Matcher(studentPrefs, studentFeatures, classCaps, classFeatures) classMatching = matcher.match() - assert matcher.isStable() print("matching is stable\n") - - # create a context to store the results - result = Context() - result["courses"] = [] # list of courses - #print out some classes and students for (course, student_list) in classMatching.items(): print(course + ", cap: " + str(classCaps[course]) + ", features: ", classFeatures[course]) this_course = Course.objects.filter(name = course).first() - mentor_list = [] for s_rin in student_list: this_student = Mentor.objects.filter(RIN = s_rin).first() item = Grade.objects.filter(student = this_student, course = this_course).first() @@ -614,15 +594,6 @@ def StartMatch(request): #assign the course to this student this_student.mentored_course = this_course this_student.save() - - new_mentor = {"name": this_student.first_name+" "+this_student.last_name, "GPA": this_student.GPA, "grade": item.student_grade.upper(), "Exp": str(item.mentor_exp)} - mentor_list.append(new_mentor) - - print() - result["courses"].append({"name": str(this_course), - "number": str(this_course.number), - "features": classFeatures[course], - "mentors": mentor_list}) unmatchedClasses = set(classes) - classMatching.keys() unmatchedStudents = set(students) - matcher.studentMatching.keys() @@ -653,6 +624,12 @@ def viewMatchResult(): return result +def getMentorAdmin(request): + AdminList = ["apple", "banana", "cherry"] + + #if (request.user.email in ) + return False + # function to get preference order from a string # String orderStr # Question question From 740e64dcacab683e15fc0159803dff13a6130f2a Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 23 Nov 2019 17:22:31 -0500 Subject: [PATCH 31/44] changes --- compsocsite/mentors/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index aadb6666..71e48815 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -516,6 +516,7 @@ def addStudentRandom(request): new_applicant.course_pref[new_applicant.RIN] = r.sample(classes, r.randint(1, numClass)) ''' new_applicant.course_pref = r.sample(classes, r.randint(1, numClass)) + print(new_applicant.course_pref) new_applicant.save() for course in Course.objects.all(): @@ -572,7 +573,7 @@ def StartMatch(request): classes = [c.name for c in Course.objects.all()] classCaps = {c.name: c.mentor_cap for c in Course.objects.all()} students = [s.RIN for s in Mentor.objects.all()] - studentPrefs = {s.RIN: [n.strip() for n in ast.literal_eval(s.course_pref)] for s in Mentor.objects.all()} + studentPrefs = {s.RIN: ast.literal_eval(s.course_pref) for s in Mentor.objects.all()} classFeatures = {c.name: (c.feature_cumlative_GPA, c.feature_course_GPA, c.feature_has_taken, c.feature_mentor_exp) for c in Course.objects.all()} matcher = Matcher(studentPrefs, studentFeatures, classCaps, classFeatures) classMatching = matcher.match() From a607483ba5a24dec9764694f4d56c83cc05456b9 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 23 Nov 2019 19:56:59 -0500 Subject: [PATCH 32/44] Add page lock to prevent form crash --- compsocsite/mentors/views.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 71e48815..f5c3290d 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -137,6 +137,7 @@ def get_queryset(self): # apply step def applystep(request): + this_user = request.user.userprofile p = this_user.mentor_profile @@ -193,6 +194,8 @@ def applystep(request): # Compensation agreement def applystep2(request): + if (checkPage(request)): + return checkPage(request) this_user = request.user.userprofile p = this_user.mentor_profile initial={ @@ -224,6 +227,8 @@ def applystep2(request): # Course grade and mentor experience def applystep3(request): + if (checkPage(request)): + return checkPage(request) this_user = request.user.userprofile p = this_user.mentor_profile @@ -275,10 +280,12 @@ def applystep3(request): # Course preference def applystep4(request): + if (checkPage(request)): + return checkPage(request) this_user = request.user.userprofile p = this_user.mentor_profile - initial={ 'pref_order': request.session.get('pref_order', p.course_pref if this_user.mentor_applied else None),} + initial={ 'pref_order': p.course_pref if this_user.mentor_applied else request.session.get('pref_order', None),} if (this_user.mentor_applied): prefer_list = ast.literal_eval(p.course_pref) print(len(prefer_list)) @@ -314,6 +321,8 @@ def applystep4(request): # Time slots page def applystep5(request): + if (checkPage(request)): + return checkPage(request) this_user = request.user.userprofile p = this_user.mentor_profile initial={ @@ -341,6 +350,7 @@ def applystep5(request): # Students Additional Page def applystep6(request): + checkPage(request) this_user = request.user.userprofile p = this_user.mentor_profile initial={ 'relevant_info': p.relevant_info if this_user.mentor_applied else request.session.get('relevant_info', None),} @@ -371,6 +381,25 @@ def breakties(order_str): return l + +# Check whether the student finsihed the previous part of the form to prevent the website crash +def checkPage(request): + if (not request.user.userprofile.mentor_applied): + for k in ['RIN', 'first_name', 'last_name', 'GPA', 'email', 'phone', 'recommender']: + if (request.session.get(k, None) == None): + return HttpResponseRedirect(reverse('mentors:index')) + for k in ['compensation', 'studnet_status', 'employed_paid_before']: + if (request.session.get(k, None) == None): + return HttpResponseRedirect(reverse('mentors:index')) + for k in ['pref_order']: + if (request.session.get(k, None) == None): + return HttpResponseRedirect(reverse('mentors:index')) + for k in ['time_slots', 'other_times']: + if (request.session.get(k, None) == None): + return HttpResponseRedirect(reverse('mentors:index')) + return False + + def submit_application(request): new_applicant = Mentor() @@ -631,6 +660,8 @@ def getMentorAdmin(request): #if (request.user.email in ) return False + + # function to get preference order from a string # String orderStr # Question question From 52dfd9cb483053746aceebcfb1de64eb7fab8f17 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sat, 23 Nov 2019 21:33:46 -0500 Subject: [PATCH 33/44] Bugs fix --- .../mentors/templates/mentors/index.html | 7 ++- compsocsite/mentors/views.py | 62 ++++++++++--------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index 4d7c4890..bdf579d1 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -78,6 +78,7 @@
            +{% if admin %}
            @@ -86,7 +87,7 @@
            - Check your courses: + Check your courses:   View
            @@ -97,7 +98,7 @@
            - Admin + Administrator Page
            @@ -126,4 +127,6 @@
            {% endif %} +{% endif %} {% endblock %} + diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index f5c3290d..e046d933 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -45,13 +45,14 @@ def get_context_data(self, **kwargs): # check if there exist a mentor application ctx['applied'] = self.request.user.userprofile.mentor_applied print(ctx['applied']) + ctx['admin'] = getMentorAdmin(self.request) return ctx def get_queryset(self): return Mentor.objects.all() def viewindex(request): - return render(request, 'mentors/index.html', {'applied': request.user.userprofile.mentor_applied}) + return render(request, 'mentors/index.html', {'applied': request.user.userprofile.mentor_applied, 'admin': getMentorAdmin(request)}) class ApplyView(views.generic.ListView): template_name = 'mentors/apply.html' @@ -194,8 +195,8 @@ def applystep(request): # Compensation agreement def applystep2(request): - if (checkPage(request)): - return checkPage(request) + if (checkPage(request, 2)): + return checkPage(request, 2) this_user = request.user.userprofile p = this_user.mentor_profile initial={ @@ -227,8 +228,8 @@ def applystep2(request): # Course grade and mentor experience def applystep3(request): - if (checkPage(request)): - return checkPage(request) + if (checkPage(request, 3)): + return checkPage(request, 3) this_user = request.user.userprofile p = this_user.mentor_profile @@ -280,8 +281,8 @@ def applystep3(request): # Course preference def applystep4(request): - if (checkPage(request)): - return checkPage(request) + if (checkPage(request, 4)): + return checkPage(request, 4) this_user = request.user.userprofile p = this_user.mentor_profile @@ -321,8 +322,8 @@ def applystep4(request): # Time slots page def applystep5(request): - if (checkPage(request)): - return checkPage(request) + if (checkPage(request, 5)): + return checkPage(request, 5) this_user = request.user.userprofile p = this_user.mentor_profile initial={ @@ -350,7 +351,8 @@ def applystep5(request): # Students Additional Page def applystep6(request): - checkPage(request) + if (checkPage(request, 6)): + return checkPage(request, 6) this_user = request.user.userprofile p = this_user.mentor_profile initial={ 'relevant_info': p.relevant_info if this_user.mentor_applied else request.session.get('relevant_info', None),} @@ -383,20 +385,21 @@ def breakties(order_str): # Check whether the student finsihed the previous part of the form to prevent the website crash -def checkPage(request): +def checkPage(request, page): if (not request.user.userprofile.mentor_applied): - for k in ['RIN', 'first_name', 'last_name', 'GPA', 'email', 'phone', 'recommender']: - if (request.session.get(k, None) == None): - return HttpResponseRedirect(reverse('mentors:index')) - for k in ['compensation', 'studnet_status', 'employed_paid_before']: - if (request.session.get(k, None) == None): - return HttpResponseRedirect(reverse('mentors:index')) - for k in ['pref_order']: - if (request.session.get(k, None) == None): - return HttpResponseRedirect(reverse('mentors:index')) - for k in ['time_slots', 'other_times']: + if (page == 2): + keys = ['RIN', 'first_name', 'last_name', 'GPA', 'email', 'phone', 'recommender'] + elif (page == 4 or page == 3): + keys = ['RIN', 'first_name', 'last_name', 'GPA', 'email', 'phone', 'recommender', 'compensation', 'studnet_status', 'employed_paid_before'] + elif (page == 5): + keys = ['RIN', 'first_name', 'last_name', 'GPA', 'email', 'phone', 'recommender','compensation', 'studnet_status', 'employed_paid_before', 'pref_order'] + elif (page == 6): + keys = ['RIN', 'first_name', 'last_name', 'GPA', 'email', 'phone', 'recommender','compensation', 'studnet_status', 'employed_paid_before', 'pref_order', 'time_slots', 'other_times'] + + for k in keys: if (request.session.get(k, None) == None): return HttpResponseRedirect(reverse('mentors:index')) + return False @@ -470,8 +473,8 @@ def withdraw(request): print('Can not delete mentor application') # Clear sessions # request.session.flush() - return render(request, 'mentors/index.html', {'applied': False}) - + HttpResponseRedirect(reverse('mentors:index')) + # load CS_Course.csv def addcourse(request): if request.method == 'POST': @@ -493,7 +496,7 @@ def addcourse(request): ) print(row[0] + " " + row[1] + " " + row[2] + " successfully added.") - return render(request, 'mentors/index.html', {}) + return HttpResponseRedirect(reverse('mentors:index')) # Return the course searched def searchCourse(request): @@ -527,7 +530,7 @@ def addStudentRandom(request): numClass = len(Course.objects.all()) if request.method == 'POST': - + Mentor.objects.all().delete() num_students = request.POST['num_students'] for i in range(int(num_students)): new_applicant = Mentor() @@ -567,7 +570,7 @@ def addStudentRandom(request): #print("Add a new student: " + new_applicant.first_name + new_applicant.last_name + ": GPA: " + str(new_applicant.GPA)) print("students now: " + str(len(Mentor.objects.all()))) - return render(request, 'mentors/index.html', {}) + return HttpResponseRedirect(reverse('mentors:index')) def StartMatch(request): @@ -655,9 +658,10 @@ def viewMatchResult(): def getMentorAdmin(request): - AdminList = ["apple", "banana", "cherry"] - - #if (request.user.email in ) + Admin_Email_List = ["cheny42@rpi.edu", "xial@rpi.edu", "hulbes@rpi.edu", "goldsd3@rpi.edu" ] + if (request.user.email.strip() in Admin_Email_List): + return True + print(request.user.email.strip()) return False From 7930935b350ac7c65137990a543e884b02e78779 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 24 Nov 2019 19:44:03 -0500 Subject: [PATCH 34/44] Change csv file update from local --- compsocsite/appauth/templates/settings.html | 2 +- compsocsite/mentors/CS_Course.csv | 20 +- compsocsite/mentors/forms.py | 41 ++-- .../migrations/0002_course_time_slots.py | 18 ++ .../0003_mentor_mentored_non_cs_bf.py | 18 ++ compsocsite/mentors/models.py | 3 +- .../templates/mentors/course_feature.html | 4 + .../mentors/templates/mentors/index.html | 28 ++- .../templates/mentors/view_students.html | 31 +++ compsocsite/mentors/urls.py | 26 ++- compsocsite/mentors/views.py | 179 ++++++++++-------- compsocsite/polls/templates/polls/base.html | 2 +- compsocsite/polls/templates/polls/index.html | 7 +- 13 files changed, 255 insertions(+), 124 deletions(-) create mode 100644 compsocsite/mentors/migrations/0002_course_time_slots.py create mode 100644 compsocsite/mentors/migrations/0003_mentor_mentored_non_cs_bf.py create mode 100755 compsocsite/mentors/templates/mentors/view_students.html diff --git a/compsocsite/appauth/templates/settings.html b/compsocsite/appauth/templates/settings.html index d8bdc70b..a434d01e 100755 --- a/compsocsite/appauth/templates/settings.html +++ b/compsocsite/appauth/templates/settings.html @@ -29,7 +29,7 @@
            - +
            diff --git a/compsocsite/mentors/CS_Course.csv b/compsocsite/mentors/CS_Course.csv index 44ea7607..85ef178e 100644 --- a/compsocsite/mentors/CS_Course.csv +++ b/compsocsite/mentors/CS_Course.csv @@ -1,10 +1,10 @@ -Computer Science I,CSCI,1100,P_1 -Beginning Prog for Engineers,CSCI,1190,P_2 -Data Structures,CSCI,1200,P_3 -Foundations of Computer Science,CSCI,2200,P_4 -Intro to Algorithms,CSCI,2300,P_5 -Computer Organization,CSCI,2500,P_6 -Principles of Software,CSCI,2600,P_7 -Intro to AI,CSCI,4150,P_8 -Operation Systems,CSCI,4210,P_9 -Database Systems,CSCI,4380,P_10 \ No newline at end of file +Computer Science I,CSCI,1100,"['M 4:00-4:50PM','M 5:00-5:50PM','M 6:00-6:50PM', 'T 10:00-11:50AM', 'T 12:00-1:50PM', 'T 2:00-3:50PM', 'T 4:00-4:50PM', 'T 5:00-5:50PM', 'T 6:00-6:50PM', 'W 10:00-11:50AM', 'W 12:00-1:50PM', 'W 2:00-3:50PM', 'W 4:00-5:50PM']" +Beginning Prog for Engineers,CSCI,1190,"['M 4:00-5:50PM', 'R 4:00-5:50PM', 'R 6:00-7:50PM']" +Data Structures,CSCI,1200,"['M 4:00-4:50PM', 'M 5:00-5:50PM', 'M 6:00-6:50PM', 'T 4:00-4:50PM', 'T 5:00-5:50PM', 'T 6:00-6:50PM', 'W 10:00-11:50AM', 'W 12:00-1:50PM', 'W 2:00-3:50PM', 'W 4:00-5:50PM', 'W 6:00-7:50PM']" +Foundations of Computer Science,CSCI,2200,"['W 10:00-10:50AM', 'W 11:00-11:50AM', 'W 12:00-12:50PM', 'W 1:00-1:50PM', 'W 4:00-4:50PM']" +Intro to Algorithms,CSCI,2300,"['W 10:00-11:50AM', 'W 12:00-1:50PM', 'W 2:00-3:50PM', 'W 4:00-5:50PM']" +Computer Organization,CSCI,2500,"['W 10:00-11:50AM', 'W 12:00-1:50PM', 'W 2:00-3:50PM']" +Principles of Software,CSCI,2600, +Intro to AI,CSCI,4150, +Operation Systems,CSCI,4210, +Database Systems,CSCI,4380, diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 22d6efb9..2ac84882 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -31,7 +31,9 @@ def __init__(self, *args, **kwargs): self.helper = FormHelper() self.fields['recommender'].required = False self.helper.layout = Layout( - HTML('== PERSONAL INFORMATION =='), + HTML('''

            PERSONAL INFORMATION

            '''), + HTML("""
            """), + HTML("
            "), Div('RIN', css_class = "inputline"), HTML("""
            """), @@ -118,7 +120,10 @@ def __init__(self, *args, **kwargs): self.helper.add_input(Button('prev', 'Prev', onclick="window.history.go(-1); return false;")) self.helper.add_input(Submit('next', 'Next')) -class MentorApplicationfoForm_step3(forms.Form): +class MentorApplicationfoForm_step3(ModelForm): + class Meta: + model = Mentor + fields = ( 'mentored_non_cs_bf',) def __init__(self, *args, **kwargs): super(MentorApplicationfoForm_step3, self).__init__(*args, **kwargs) self.helper = FormHelper() @@ -138,12 +143,20 @@ def __init__(self, *args, **kwargs): ('d+', 'D+'), ('d', 'D'), ('f', 'F'), ('p', 'Progressing'), ('n', 'Not Taken'), ) + grades_ap = ( + ('a', 'A'), ('a-', 'A-'), + ('b+', 'B+'), ('b', 'B'), ('b-', 'B-'), + ('c+', 'C+'), ('c', 'C'), ('c-', 'C-'), + ('d+', 'D+'), ('d', 'D'), ('f', 'F'), + ('p', 'Progressing'), ('ap', 'AP'), ('n', 'Not Taken'), + ) choices_YN = ( ('Y', 'YES'), ('N', 'NO'), ) + self.fields[course.name+ "_grade"] = forms.ChoiceField( - choices = grades, + choices = grades_ap if course.number == '1100' else grades, label = "Grade on this course:", ) #self.initial[course.name+ "_grade"] = 'n' @@ -162,7 +175,9 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( - HTML('== COURSE EXPERIENCE =='), + HTML('''

            COURSE EXPERIENCE

            '''), + HTML("""
            """), + HTML("
            "), HTML('''
            Below is a list of courses likely to need mentors. Note that CSCI 1100 is in Python, CSCI 1190 is in MATLAB, CSCI 1200 is in C++, CSCI 2300 is in Python/C++, CSCI 2500 @@ -174,6 +189,9 @@ def __init__(self, *args, **kwargs): be sure to describe equivalent courses or experiences in the general comments textbox further below.
            '''), HTML("""
            """), + HTML(''''''), + Div('mentored_non_cs_bf'), + HTML("""
            """), course_layout, HTML("
            "), ) @@ -191,8 +209,8 @@ def __init__(self, *args, **kwargs): self.fields["pref_order"].required = False self.helper.layout = Layout( - HTML('== COURSE PREFERENCE =='), - + HTML('''

            COURSE PREFERENCE

            '''), + HTML("""
            """), HTML("
            "), HTML('''
            Select the courses you would like to mentor and the courses you do not prefer. For the course you would like to mentor, please change their rankings by @@ -293,9 +311,9 @@ def __init__(self, *args, **kwargs): ('W_2:00-3:50PM', 'W 2:00-3:50PM'), ('W_4:00-4:50PM', 'W 4:00-4:50PM'), ('W_5:00-5:50AM', 'W 5:00-5:50AM'), - ('W_6:00-6:50PM', ' W 6:00-7:50PM'), - ('T_4:00-5:50AM', 'T 4:00-5:50AM'), - ('T_6:00-6:50PM', 'T 6:00-7:50PM'), + ('W_6:00-6:50PM', 'W 6:00-7:50PM'), + ('R_4:00-5:50AM', 'R 4:00-5:50AM'), + ('R_6:00-6:50PM', 'R 6:00-7:50PM'), ) self.fields["time_slots"] = forms.MultipleChoiceField( choices=time_slots_choices, @@ -306,7 +324,8 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( - HTML('== SCHEDULING =='), + HTML('''

            SCHEDULING

            '''), + HTML("""
            """), HTML("
            "), HTML('''
            Indicate your availability for the Spring 2020 semester by carefully checking all boxes that apply. The more boxes you check, @@ -339,7 +358,7 @@ def __init__(self, *args, **kwargs): self.fields["relevant_info"] = forms.CharField(widget=forms.Textarea(attrs={'cols': 100, 'rows': 8})) self.helper.layout = Layout( - HTML('== ADDITIONAL INFOMATION =='), + HTML('''

            ADDITIONAL INFOMATION

            '''), HTML("
            "), HTML('''
            Please provide any other relevant information about yourself in the space below, including your mentoring preferences (i.e., which courses you'd prefer to mentor, which courses you'd prefer not to mentor, other extracurricular CS activities you're involved with, etc.). Do not leave this blank.
            '''), Field('relevant_info', style = "width:100%"), diff --git a/compsocsite/mentors/migrations/0002_course_time_slots.py b/compsocsite/mentors/migrations/0002_course_time_slots.py new file mode 100644 index 00000000..3a6c5dd1 --- /dev/null +++ b/compsocsite/mentors/migrations/0002_course_time_slots.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-11-24 17:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='time_slots', + field=models.CharField(default='[]', max_length=500), + ), + ] diff --git a/compsocsite/mentors/migrations/0003_mentor_mentored_non_cs_bf.py b/compsocsite/mentors/migrations/0003_mentor_mentored_non_cs_bf.py new file mode 100644 index 00000000..d62cb009 --- /dev/null +++ b/compsocsite/mentors/migrations/0003_mentor_mentored_non_cs_bf.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-11-24 21:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0002_course_time_slots'), + ] + + operations = [ + migrations.AddField( + model_name='mentor', + name='mentored_non_cs_bf', + field=models.BooleanField(default=False), + ), + ] diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index 31f62cbd..eb23d472 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -188,6 +188,7 @@ class Course(models.Model): # This should change to foreignkey afterwards # But we left this now to make the implementation easy instructor = models.CharField(max_length=50, default = 'none') # Instrcutor's name + time_slots = models.CharField(max_length=500, default = '[]') # Instrcutor's name # feature weights to represent the pref feature_cumlative_GPA = models.IntegerField(default=0) @@ -232,7 +233,7 @@ class Mentor(models.Model): ) studnet_status = models.CharField(max_length=1, choices = status, default='i') employed_paid_before = models.BooleanField(default = False) - + mentored_non_cs_bf = models.BooleanField(default = False) ''' time_slots_choices = ( ('M_4:00-4:50PM', 'M 4:00-4:50PM'), diff --git a/compsocsite/mentors/templates/mentors/course_feature.html b/compsocsite/mentors/templates/mentors/course_feature.html index 3dc8c749..bc979ce6 100755 --- a/compsocsite/mentors/templates/mentors/course_feature.html +++ b/compsocsite/mentors/templates/mentors/course_feature.html @@ -33,7 +33,11 @@ {% csrf_token %}
            {{choosen_course.name}}:
            +
            Time Slots:
            + {% for time in time_slots.times%} +
            {{time}}
            + {% endfor %}
            Capacity: {{choosen_course.mentor_cap}}
              diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index bdf579d1..0f55c272 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -62,13 +62,15 @@ View your application and make changes:   View and Change - View + {% if dev %} + View + {% endif %}

              You can withdraw your application if you do not want to apply for mentor anymore. A new application can be started after you withdrawed.

              - +
              {% else %} Begin to apply:   @@ -78,6 +80,7 @@
            + {% if admin %}
            @@ -101,24 +104,39 @@ Administrator Page
            -
            + {% csrf_token %} - Add Course (DEV): - +

            You can upload csv and generate new courses.

            +

            For the old courses, you can change course name, mentor cap and time slots(['t1', 't2'...]).

            +

            But you can not delete them right now for the sake of secruity

            + Load courses from upload:   + + +
            +
            + + {% csrf_token %} + View all the applicants:   + View +
            + {% if dev %}
            {% csrf_token %} Add Student Random (DEV):
            + {% endif %}
            {% csrf_token %} Match:
            +
            +
            {% csrf_token %} diff --git a/compsocsite/mentors/templates/mentors/view_students.html b/compsocsite/mentors/templates/mentors/view_students.html new file mode 100755 index 00000000..59f2a0f2 --- /dev/null +++ b/compsocsite/mentors/templates/mentors/view_students.html @@ -0,0 +1,31 @@ +{% extends 'polls/base.html' %} + +{% block content %} +{% if user.is_authenticated %} + +
            +
            +
            + Applicants +
            + Number of Applicants:   {{ applicants_number }} + + {% csrf_token %} + Add Course (DEV):   + + + +
            +
            + {% for applicant in applicants %} +
            + RIN: {{ applicant.RIN }}   {{ applicant }}   {{ applicant.email }}  {{ applicant.phone }}   GPA: {{ applicant.GPA }}   Recommender: {{ applicant.recommender }} +
            + {% endfor %} + +
            +
            +
            +
            +{% endif %} +{% endblock %} diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index 9ad3a8e2..d866d383 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -9,33 +9,31 @@ app_name = 'mentors' urlpatterns = [ url(r'^$', login_required(views.viewindex), name='index'), - url(r'^apply$', login_required(views.ApplyView.as_view()), name='apply'), - # personal info - url(r'^apply/$', views.applystep, name='applyfunc1'), + #url(r'^apply$', login_required(views.ApplyView.as_view()), name='apply'), + + # Mentor applciation steps 1-6 + url(r'^apply/$', login_required(views.applystep), name='applyfunc1'), url(r'^applystep2/$', login_required(views.applystep2), name='applystep2'), url(r'^applystep3/$', login_required(views.applystep3), name='applystep3'), url(r'^applystep4/$', login_required(views.applystep4), name='applystep4'), url(r'^applystep5/$', login_required(views.applystep5), name='applystep5'), url(r'^applystep6/$', login_required(views.applystep6), name='applystep6'), + url(r'^view-course$', login_required(views.CourseFeatureView.as_view()), name='view-course'), url(r'^view-course-result$', login_required(views.viewResultPage), name='view-course-result'), - # compensation and responsbility - #url(r'^applyfunc2/$', views.applystep, name='applyfunc2'), - # RANKING of preference of course of studnet - - url(r'^addcoursefunc/$', views.addcourse, name='addcoursefunc'), + url(r'^addcoursefunc/$', login_required(views.addcourse), name='addcoursefunc'), url(r'^addStudentRandomfunc/$', views.addStudentRandom, name='addStudentRandomfunc'), - url(r'^matchfunc/$', views.StartMatch, name='matchfunc'), + url(r'^matchfunc/$', login_required(views.StartMatch), name='matchfunc'), - url(r'^searchcoursefunc/$', views.searchCourse, name='searchcoursefunc'), - url(r'^changefeaturefunc/$', views.changeFeature, name='changefeaturefunc'), + url(r'^searchcoursefunc/$', login_required(views.searchCourse), name='searchcoursefunc'), + url(r'^changefeaturefunc/$', login_required(views.changeFeature), name='changefeaturefunc'), - url(r'^view_application$', login_required(views.view_applyView.as_view()), name='view_application'), + url(r'^view_application$', login_required(views.viewApplictionView.as_view()), name='view_application'), + url(r'^view-students$', login_required(views.viewStudentsView.as_view()), name='view-students'), + url(r'^download-mentor-csv/$', login_required(views.download_mentor_csv), name='download-mentor-csv'), url(r'^withdrawfunc/$', views.withdraw, name='withdrawfunc'), - url(r'^apply_personal_info$', login_required(views.ApplyPersonalInfoView.as_view()), name='apply_personal_info'), - url(r'^apply_compensation$', login_required(views.ApplyCompensationView.as_view()), name='apply_compensation'), #url(r'^apply_prefernece/$', login_required(views.CourseAutocomplete.as_view()), name='apply_prefernece'), ] diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index e046d933..5ef99d09 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -5,6 +5,7 @@ import os import time import collections +import dateutil.parser from django.shortcuts import render, get_object_or_404, redirect from django.http import HttpResponseRedirect, HttpResponse, HttpRequest @@ -45,14 +46,14 @@ def get_context_data(self, **kwargs): # check if there exist a mentor application ctx['applied'] = self.request.user.userprofile.mentor_applied print(ctx['applied']) - ctx['admin'] = getMentorAdmin(self.request) + ctx['admin'] = isMentorAdmin(self.request) return ctx def get_queryset(self): return Mentor.objects.all() def viewindex(request): - return render(request, 'mentors/index.html', {'applied': request.user.userprofile.mentor_applied, 'admin': getMentorAdmin(request)}) + return render(request, 'mentors/index.html', {'applied': request.user.userprofile.mentor_applied, 'admin': isMentorAdmin(request)}) class ApplyView(views.generic.ListView): template_name = 'mentors/apply.html' @@ -64,55 +65,24 @@ def get_context_data(self, **kwargs): def get_queryset(self): return Mentor.objects.all() -class view_applyView(views.generic.ListView): +class viewApplictionView(views.generic.ListView): template_name = 'mentors/view_application.html' def get_context_data(self, **kwargs): - ctx = super(view_applyView, self).get_context_data(**kwargs) - - return ctx - def get_queryset(self): - return Mentor.objects.all() - -# view of personal infomation page -class ApplyPersonalInfoView(views.generic.ListView): - template_name = 'mentors/apply_personal_info.html' - def get_context_data(self, **kwargs): - ctx = super(ApplyPersonalInfoView, self).get_context_data(**kwargs) - # check if there exist a mentor application - ctx['applied'] = Mentor.applied - ctx['applications'] = Mentor.objects.all() - - return ctx - def get_queryset(self): - return Mentor.objects.all() - -# view of compensation and reponsibility page -class ApplyCompensationView(views.generic.ListView): - template_name = 'mentors/apply_compensation.html' - def get_context_data(self, **kwargs): - ctx = super(ApplyCompensationView, self).get_context_data(**kwargs) - # check if there exist a mentor application - ctx['applied'] = Mentor.applied - ctx['applications'] = Mentor.objects.all() - ctx['step'] = Mentor.step - + ctx = super(viewApplictionView, self).get_context_data(**kwargs) + ctx['user'] = self.request.user.userprofile.mentor_profile return ctx - def get_queryset(self): return Mentor.objects.all() -# view of preference of a student applicant page -class ApplyPreferenceView(views.generic.ListView): - template_name = 'mentors/apply_preference.html' +# Admin to view all the applications +class viewStudentsView(views.generic.ListView): + template_name = 'mentors/view_students.html' def get_context_data(self, **kwargs): - ctx = super(ApplyPreferenceView, self).get_context_data(**kwargs) - # check if there exist a mentor application - ctx['applied'] = Mentor.applied - ctx['applications'] = Mentor.objects.all() - ctx['step'] = Mentor.step - # === need more for course data === + ctx = super(viewStudentsView, self).get_context_data(**kwargs) + ctx['isAdmin'] = isMentorAdmin(self.request) + ctx['applicants'] = Mentor.objects.all() + ctx['applicants_number'] = len(Mentor.objects.all()) return ctx - def get_queryset(self): return Mentor.objects.all() @@ -233,7 +203,7 @@ def applystep3(request): this_user = request.user.userprofile p = this_user.mentor_profile - initial={} + initial={ 'mentored_non_cs_bf': p.mentored_non_cs_bf if this_user.mentor_applied else request.session.get('mentored_non_cs_bf', None)} if (this_user.mentor_applied): for course in Course.objects.all(): this_grade = Grade.objects.filter(course = course, student = p).first() @@ -254,6 +224,7 @@ def applystep3(request): if request.method == 'POST': if form.is_valid(): if (this_user.mentor_applied): + p.mentored_non_cs_bf = form.cleaned_data['mentored_non_cs_bf'] for course in Course.objects.all(): course_grade = course.name + "_grade" course_exp = course.name + "_exp" @@ -268,6 +239,7 @@ def applystep3(request): this_grade.have_taken = False this_grade.save() else: + request.session['mentored_non_cs_bf'] = form.cleaned_data['mentored_non_cs_bf'] for course in Course.objects.all(): course_grade = course.name + "_grade" course_exp = course.name + "_exp" @@ -287,14 +259,8 @@ def applystep4(request): p = this_user.mentor_profile initial={ 'pref_order': p.course_pref if this_user.mentor_applied else request.session.get('pref_order', None),} - if (this_user.mentor_applied): - prefer_list = ast.literal_eval(p.course_pref) - print(len(prefer_list)) - print((prefer_list)) - - else: - prefer_list = ast.literal_eval(request.session.get('pref_order', '[]')) - + prefer_list = ast.literal_eval(p.course_pref if this_user.mentor_applied else request.session.get('pref_order', '[]')) + course_list = [c.name for c in Course.objects.all()] pref_courses = Course.objects.filter(name__in=prefer_list) not_pref_courses = Course.objects.filter(name__in=[item for item in course_list if item not in prefer_list]) @@ -399,7 +365,6 @@ def checkPage(request, page): for k in keys: if (request.session.get(k, None) == None): return HttpResponseRedirect(reverse('mentors:index')) - return False @@ -417,7 +382,8 @@ def submit_application(request): new_applicant.studnet_status = request.session["studnet_status"] new_applicant.employed_paid_before = request.session["employed_paid_before"] - new_applicant.course_pref = (request.session["pref_order"]) + new_applicant.mentored_non_cs_bf = request.session["mentored_non_cs_bf"] + new_applicant.course_pref = request.session["pref_order"] new_applicant.time_slots = request.session["time_slots"] new_applicant.other_times = request.session["other_times"] new_applicant.relevant_info = request.session["relevant_info"] @@ -428,10 +394,6 @@ def submit_application(request): this_user.mentor_applied = True this_user.mentor_profile = new_applicant this_user.save() - - - for i in this_user.mentor_profile.course_pref: - print(i) #orderStr = self.cleaned_data["pref_order"] # Save Grades on the course average @@ -466,7 +428,6 @@ def withdraw(request): try: #request.user.userprofile.mentor_profile.delete() request.user.userprofile.mentor_applied = False - print(request.user.userprofile.mentor_applied) request.user.userprofile.save() request.user.save() except: @@ -475,26 +436,30 @@ def withdraw(request): # request.session.flush() HttpResponseRedirect(reverse('mentors:index')) -# load CS_Course.csv +# load CS_Course.csv to generate courses def addcourse(request): - if request.method == 'POST': - Course.objects.all().delete() - #print(new_course.class_title + new_course.class_number + new_course.class_name + "successfully added") - with open("mentors/CS_Course.csv") as f: - reader = csv.reader(f) - for row in reader: - _ = Course.objects.get_or_create( - name = row[0], - subject = row[1], - number = row[2], - instructor = row[3], - mentor_cap = r.randint(3, 10), - feature_cumlative_GPA = r.randint(3, 10), - feature_has_taken = r.randint(0, 10), - feature_course_GPA = r.randint(3, 10), - feature_mentor_exp = r.randint(0, 10), - ) - print(row[0] + " " + row[1] + " " + row[2] + " successfully added.") + #Course.objects.all().delete() + #print(new_course.class_title + new_course.class_number + new_course.class_name + "successfully added") + if request.method == 'POST' and request.FILES['myfile']: + file = request.FILES['myfile'] + decoded_file = file.read().decode('utf-8').splitlines() + reader = csv.reader(decoded_file) + + for row in reader: + + c, created = Course.objects.get_or_create( + name = row[0], + subject = row[1], + number = row[2], + ) + c.time_slots = row[4] if row[4].strip()!="" else "[]" + c.mentor_cap = row[3] + c.feature_cumlative_GPA = 1 + c.feature_has_taken = 1 + c.feature_course_GPA = 1 + c.feature_mentor_exp = 1 + c.save() + print(row[0] + " " + row[1] + " " + row[2] +" "+ row[3] + " successfully added/changed.") return HttpResponseRedirect(reverse('mentors:index')) @@ -504,7 +469,11 @@ def searchCourse(request): if request.method == 'POST': course_name = request.POST.get('courses', False) choosen_course = Course.objects.filter(name = course_name).first() - return render(request, 'mentors/course_feature.html', {'courses': courses, 'choosen_course': choosen_course}) + time_slots = Context() + time_slots["times"] = [] + for t in ast.literal_eval(choosen_course.time_slots): + time_slots["times"].append(t) + return render(request, 'mentors/course_feature.html', {'courses': courses, 'choosen_course': choosen_course, 'time_slots': time_slots}) # Change the value of features of a selected course @@ -657,13 +626,63 @@ def viewMatchResult(): return result -def getMentorAdmin(request): +def isMentorAdmin(request): Admin_Email_List = ["cheny42@rpi.edu", "xial@rpi.edu", "hulbes@rpi.edu", "goldsd3@rpi.edu" ] if (request.user.email.strip() in Admin_Email_List): return True print(request.user.email.strip()) return False +# csv download fucntion +def download_mentor_csv(request): + # Create the HttpResponse object with the appropriate CSV header. + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="S20-UG-mentor-applicants.csv"' + + writer = csv.writer(response) + row = ['RIN', 'Last Name', 'First Name', 'Email', 'Phone', 'GPA', 'Recommender', 'Compensation', 'Paid Before?', 'Status', 'Info', 'Mentored_nonCS'] + for course in Course.objects.all(): + course_prefix = course.subject + '_' + course.number + g = course_prefix+'_Grade' + m = course_prefix+'_Mentored' + row.append(g) + row.append(m) + + for course in Course.objects.all(): + for t in ast.literal_eval(course.time_slots): + ct = course.subject + '_' + course.number + '_' + t + row.append(ct) + row.append("Other Times") + writer.writerow(row) + comp = { + "n": "Either", + "p": "Pay", + "c": 'Credit' + } + status = { + "i": "International", + "d": "Domestic", + } + for m in Mentor.objects.all(): + row = [m.RIN, m.first_name, m.last_name, str(m.email), m.phone, m.GPA, m.recommender, comp[m.compensation], "paidbyrpi" if m.employed_paid_before else "", status[m.studnet_status], m.relevant_info.replace('"', '\'').replace('/[\n\r]+/', ' '), m.mentored_non_cs_bf] + for course in Course.objects.all(): + grade = Grade.objects.filter(course = course, student = m).first() + course_prefix = course.subject + " " + course.number + row.append(grade.student_grade.upper()) + row.append("Yes" if grade.mentor_exp else "") + for course in Course.objects.all(): + for course_time in ast.literal_eval(course.time_slots): + if(course_time in [i.replace('_', ' ') for i in ast.literal_eval( m.time_slots)]): + if (course.name in ast.literal_eval(m.course_pref)): + row.append("wantstomentor") + else: + row.append("") + else: + row.append("") + row.append(m.other_times.replace('/[\n\r]+/', '').replace('"', '\'')) + writer.writerow(row) + + return response # function to get preference order from a string diff --git a/compsocsite/polls/templates/polls/base.html b/compsocsite/polls/templates/polls/base.html index 33b7ba96..0b79f304 100755 --- a/compsocsite/polls/templates/polls/base.html +++ b/compsocsite/polls/templates/polls/base.html @@ -171,7 +171,7 @@
          • Classes
          • Groups
          • Sessions
          • -
          • TA&Mentor Apply
          • +
          • Mentor Application
          • {% if not request.flavour == "mobile" %}
          • diff --git a/compsocsite/polls/templates/polls/index.html b/compsocsite/polls/templates/polls/index.html index 69ab3a87..ddffd477 100755 --- a/compsocsite/polls/templates/polls/index.html +++ b/compsocsite/polls/templates/polls/index.html @@ -30,7 +30,12 @@
            - If you are looking to sign up to be an undergraduate CS mentor, click here. +

            +

            +
            Spring 2020 Undergraduate Programming Mentor Application is now open!
            + APPLY NOW +

            +

            From d4e9e5cc86f78f15f7e13b5a53ddd0dd74dea48a Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 24 Nov 2019 21:24:37 -0500 Subject: [PATCH 35/44] Bug fixed --- compsocsite/mentors/forms.py | 1 + compsocsite/mentors/models.py | 8 ++--- .../mentors/templates/mentors/index.html | 31 +++++++++++++++---- compsocsite/mentors/views.py | 13 ++++---- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 2ac84882..32b71372 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -359,6 +359,7 @@ def __init__(self, *args, **kwargs): self.fields["relevant_info"] = forms.CharField(widget=forms.Textarea(attrs={'cols': 100, 'rows': 8})) self.helper.layout = Layout( HTML('''

            ADDITIONAL INFOMATION

            '''), + HTML("""
            """), HTML("
            "), HTML('''
            Please provide any other relevant information about yourself in the space below, including your mentoring preferences (i.e., which courses you'd prefer to mentor, which courses you'd prefer not to mentor, other extracurricular CS activities you're involved with, etc.). Do not leave this blank.
            '''), Field('relevant_info', style = "width:100%"), diff --git a/compsocsite/mentors/models.py b/compsocsite/mentors/models.py index eb23d472..93a93c5e 100644 --- a/compsocsite/mentors/models.py +++ b/compsocsite/mentors/models.py @@ -213,11 +213,11 @@ class Mentor(models.Model): #RIN = models.CharField(max_length=9, validators=[MinLengthValidator(9)], primary_key=True) RIN = models.CharField(max_length=9, validators=[MinLengthValidator(9)]) - first_name = models.CharField(max_length=50) # first name - last_name = models.CharField(max_length=50) # last name - GPA = MinMaxFloat(min_value = 0.0, max_value = 4.0) + first_name = models.CharField(max_length=50, default="") # first name + last_name = models.CharField(max_length=50, default="") # last name + GPA = MinMaxFloat(min_value = 0.0, max_value = 4.0, default=0) email = models.CharField(max_length=50) - phone = models.CharField(max_length=50) # ??? + phone = models.CharField(max_length=10, default="") # ??? recommender = models.CharField(max_length=50, default="") # Compensation Choices diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index 0f55c272..0d604d77 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -66,12 +66,31 @@ View {% endif %}
            - -

            You can withdraw your application if you do not want to apply for mentor anymore. A new application can be started after you withdrawed.

            - -
            - -
            + +

            + You can withdraw your application if you do not want to apply for mentor anymore. A new application can be started after you withdrawed.

            + Withdraw apllication +

            + + {% else %} Begin to apply:   Apply diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 5ef99d09..babc4eac 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -377,6 +377,7 @@ def submit_application(request): new_applicant.GPA = request.session["GPA"] new_applicant.phone = request.session["phone"] new_applicant.recommender = request.session["recommender"] + new_applicant.email = request.session["email"] new_applicant.compensation = request.session["compensation"] new_applicant.studnet_status = request.session["studnet_status"] @@ -426,15 +427,16 @@ def submit_application(request): def withdraw(request): if request.method == 'GET': try: - #request.user.userprofile.mentor_profile.delete() + request.user.userprofile.mentor_profile.delete() + request.user.userprofile.mentor_profile = None request.user.userprofile.mentor_applied = False request.user.userprofile.save() - request.user.save() - except: + except Exception as e: + print(e) print('Can not delete mentor application') # Clear sessions # request.session.flush() - HttpResponseRedirect(reverse('mentors:index')) + return HttpResponseRedirect(reverse('mentors:index')) # load CS_Course.csv to generate courses def addcourse(request): @@ -446,7 +448,6 @@ def addcourse(request): reader = csv.reader(decoded_file) for row in reader: - c, created = Course.objects.get_or_create( name = row[0], subject = row[1], @@ -664,7 +665,7 @@ def download_mentor_csv(request): "d": "Domestic", } for m in Mentor.objects.all(): - row = [m.RIN, m.first_name, m.last_name, str(m.email), m.phone, m.GPA, m.recommender, comp[m.compensation], "paidbyrpi" if m.employed_paid_before else "", status[m.studnet_status], m.relevant_info.replace('"', '\'').replace('/[\n\r]+/', ' '), m.mentored_non_cs_bf] + row = [m.RIN, m.first_name, m.last_name, m.email, m.phone, m.GPA, m.recommender, comp[m.compensation], "paidbyrpi" if m.employed_paid_before else "", status[m.studnet_status], m.relevant_info.replace('"', '\'').replace('/[\n\r]+/', ' '), m.mentored_non_cs_bf] for course in Course.objects.all(): grade = Grade.objects.filter(course = course, student = m).first() course_prefix = course.subject + " " + course.number From 19574cfda97bb69ffad3b62251d694a78522eb89 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 24 Nov 2019 22:06:07 -0500 Subject: [PATCH 36/44] Finalize --- compsocsite/mentors/forms.py | 14 ++++++---- .../mentors/templates/mentors/index.html | 24 ++++++++++------- .../templates/mentors/view_match_result.html | 4 +-- .../templates/mentors/view_students.html | 13 +++------- compsocsite/mentors/urls.py | 2 +- compsocsite/mentors/views.py | 26 +++++++++---------- 6 files changed, 44 insertions(+), 39 deletions(-) diff --git a/compsocsite/mentors/forms.py b/compsocsite/mentors/forms.py index 32b71372..8e7033b2 100644 --- a/compsocsite/mentors/forms.py +++ b/compsocsite/mentors/forms.py @@ -304,16 +304,20 @@ def __init__(self, *args, **kwargs): ('T_12:00-1:50PM', 'T 12:00-1:50PM'), ('T_2:00-3:50PM', 'T 2:00-3:50PM'), ('T_4:00-4:50PM', 'T 4:00-4:50PM'), - ('T_5:00-5:50AM', 'T 5:00-5:50AM'), + ('T_5:00-5:50PM', 'T 5:00-5:50PM'), ('T_6:00-6:50PM', 'T 6:00-6:50PM'), + ('W_10:00-10:50AM', 'W 10:00-10:50AM'), ('W_10:00-11:50AM', 'W 10:00-11:50AM'), + ('W_11:00-11:50AM', 'W 11:00-11:50AM'), + ('W_12:00-12:50PM', 'W 12:00-12:50PM'), ('W_12:00-1:50PM', 'W 12:00-1:50PM'), + ('W_1:00-1:50PM', 'W 1:00-1:50PM'), ('W_2:00-3:50PM', 'W 2:00-3:50PM'), ('W_4:00-4:50PM', 'W 4:00-4:50PM'), - ('W_5:00-5:50AM', 'W 5:00-5:50AM'), - ('W_6:00-6:50PM', 'W 6:00-7:50PM'), - ('R_4:00-5:50AM', 'R 4:00-5:50AM'), - ('R_6:00-6:50PM', 'R 6:00-7:50PM'), + ('W_4:00-5:50PM', 'W 4:00-5:50PM'), + ('W_6:00-7:50PM', 'W 6:00-7:50PM'), + ('R_4:00-5:50PM', 'R 4:00-5:50PM'), + ('R_6:00-7:50PM', 'R 6:00-7:50PM'), ) self.fields["time_slots"] = forms.MultipleChoiceField( choices=time_slots_choices, diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index 0d604d77..9aac3d61 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -16,6 +16,8 @@

          • +{% if admin %} +
            @@ -100,7 +102,6 @@
            -{% if admin %}
            @@ -126,13 +127,21 @@
            {% csrf_token %}

            You can upload csv and generate new courses.

            -

            For the old courses, you can change course name, mentor cap and time slots(['t1', 't2'...]).

            +

            For the old courses, you can change mentor capacity and time slots(['t1', 't2'...]).

            But you can not delete them right now for the sake of secruity

            Load courses from upload:   - +
            +
            +
            + +
            + {% csrf_token %} + Download the csv of all applicants:   + +
            {% csrf_token %} @@ -147,19 +156,16 @@ - {% endif %}
            {% csrf_token %} - Match: + Match:  
            + View Match Result + {% endif %} -
            - {% csrf_token %} - -
            diff --git a/compsocsite/mentors/templates/mentors/view_match_result.html b/compsocsite/mentors/templates/mentors/view_match_result.html index 17fe8f9b..8dfd7cca 100755 --- a/compsocsite/mentors/templates/mentors/view_match_result.html +++ b/compsocsite/mentors/templates/mentors/view_match_result.html @@ -2,7 +2,7 @@ {% block content %} {% if user.is_authenticated %} - +{% if isAdmin %}

            {% if messages %} @@ -30,6 +30,6 @@

            - +{% endif %} {% endif %} {% endblock %} diff --git a/compsocsite/mentors/templates/mentors/view_students.html b/compsocsite/mentors/templates/mentors/view_students.html index 59f2a0f2..c3223d86 100755 --- a/compsocsite/mentors/templates/mentors/view_students.html +++ b/compsocsite/mentors/templates/mentors/view_students.html @@ -2,24 +2,18 @@ {% block content %} {% if user.is_authenticated %} - +{% if isAdmin %}
            Applicants
            - Number of Applicants:   {{ applicants_number }} -
            - {% csrf_token %} - Add Course (DEV):   - -
            -
            + Number of students applied:   {{ applicants_number }} {% for applicant in applicants %}
            - RIN: {{ applicant.RIN }}   {{ applicant }}   {{ applicant.email }}  {{ applicant.phone }}   GPA: {{ applicant.GPA }}   Recommender: {{ applicant.recommender }} + {{forloop.counter}}.  RIN: {{ applicant.RIN }}   {{ applicant }}   {{ applicant.email }}  {{ applicant.phone }}   GPA: {{ applicant.GPA }}   Recommender: {{ applicant.recommender }}
            {% endfor %} @@ -28,4 +22,5 @@
            {% endif %} +{% endif %} {% endblock %} diff --git a/compsocsite/mentors/urls.py b/compsocsite/mentors/urls.py index d866d383..2948858c 100644 --- a/compsocsite/mentors/urls.py +++ b/compsocsite/mentors/urls.py @@ -21,7 +21,7 @@ url(r'^view-course$', login_required(views.CourseFeatureView.as_view()), name='view-course'), - url(r'^view-course-result$', login_required(views.viewResultPage), name='view-course-result'), + url(r'^view-match-result$', login_required(views.MatchResultView.as_view()), name='view-match-result'), url(r'^addcoursefunc/$', login_required(views.addcourse), name='addcoursefunc'), url(r'^addStudentRandomfunc/$', views.addStudentRandom, name='addStudentRandomfunc'), diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index babc4eac..8698fbaa 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -45,7 +45,6 @@ def get_context_data(self, **kwargs): ctx = super(IndexView, self).get_context_data(**kwargs) # check if there exist a mentor application ctx['applied'] = self.request.user.userprofile.mentor_applied - print(ctx['applied']) ctx['admin'] = isMentorAdmin(self.request) return ctx @@ -100,7 +99,9 @@ class MatchResultView(views.generic.ListView): template_name = 'mentors/view_match_result.html' def get_context_data(self, **kwargs): ctx = super(MatchResultView, self).get_context_data(**kwargs) + ctx['isAdmin'] = isMentorAdmin(self.request) ctx['courses'] = Course.objects.all() + ctx['result'] = viewMatchResult() return ctx def get_queryset(self): return Course.objects.all() @@ -122,7 +123,6 @@ def applystep(request): 'recommender': p.recommender if this_user.mentor_applied else request.session.get('recommender', None) } - print(request.user.userprofile.time_creation) form = MentorApplicationfoForm_step1(request.POST or None, initial=initial) if request.method == 'POST': # initate a new mentor applicant @@ -278,7 +278,7 @@ def applystep4(request): p.save() else: request.session['pref_order'] = (form.cleaned_data['pref_order']) - print(request.session['pref_order']) + #print(request.session['pref_order']) #print(breakties(form.cleaned_data['pref_order'])) return HttpResponseRedirect(reverse('mentors:applystep5')) else: @@ -419,7 +419,7 @@ def submit_application(request): else: new_grade.have_taken = False new_grade.save() - print(new_grade.course.name + ": " + new_grade.student_grade) + #print(new_grade.course.name + ": " + new_grade.student_grade) return new_applicant @@ -460,7 +460,7 @@ def addcourse(request): c.feature_course_GPA = 1 c.feature_mentor_exp = 1 c.save() - print(row[0] + " " + row[1] + " " + row[2] +" "+ row[3] + " successfully added/changed.") + #print(row[0] + " " + row[1] + " " + row[2] +" "+ row[3] + " successfully added/changed.") return HttpResponseRedirect(reverse('mentors:index')) @@ -518,7 +518,7 @@ def addStudentRandom(request): new_applicant.course_pref[new_applicant.RIN] = r.sample(classes, r.randint(1, numClass)) ''' new_applicant.course_pref = r.sample(classes, r.randint(1, numClass)) - print(new_applicant.course_pref) + #print(new_applicant.course_pref) new_applicant.save() for course in Course.objects.all(): @@ -585,7 +585,7 @@ def StartMatch(request): #print out some classes and students for (course, student_list) in classMatching.items(): - print(course + ", cap: " + str(classCaps[course]) + ", features: ", classFeatures[course]) + #print(course + ", cap: " + str(classCaps[course]) + ", features: ", classFeatures[course]) this_course = Course.objects.filter(name = course).first() for s_rin in student_list: @@ -600,14 +600,15 @@ def StartMatch(request): unmatchedClasses = set(classes) - classMatching.keys() unmatchedStudents = set(students) - matcher.studentMatching.keys() - print(f"{len(unmatchedClasses)} classes with no students") - print(f"{len(unmatchedStudents)} students not in a class") + #print(f"{len(unmatchedClasses)} classes with no students") + #print(f"{len(unmatchedStudents)} students not in a class") + return HttpResponseRedirect(reverse('mentors:view-match-result')) - return render(request, 'mentors/view_match_result.html', {'result': viewMatchResult()}) + #return render(request, 'mentors/view_match_result.html', {'result': viewMatchResult()}) def viewResultPage(request): - return render(request, 'mentors/view_match_result.html', {'result': viewMatchResult()}) + return HttpResponseRedirect(reverse('mentors:view-match-result')) def viewMatchResult(): # create a context to store the results @@ -628,10 +629,9 @@ def viewMatchResult(): def isMentorAdmin(request): - Admin_Email_List = ["cheny42@rpi.edu", "xial@rpi.edu", "hulbes@rpi.edu", "goldsd3@rpi.edu" ] + Admin_Email_List = ["zahavg@rpi.edu", "qianj2@rpi.edu", "cheny42@rpi.edu", "xial@rpi.edu", "hulbes@rpi.edu", "goldsd3@rpi.edu" ] if (request.user.email.strip() in Admin_Email_List): return True - print(request.user.email.strip()) return False # csv download fucntion From 163600587050c81ac902ffb0929716e4ec35e2a4 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 24 Nov 2019 22:28:35 -0500 Subject: [PATCH 37/44] Finalize --- compsocsite/mentors/CS_Course.csv | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 compsocsite/mentors/CS_Course.csv diff --git a/compsocsite/mentors/CS_Course.csv b/compsocsite/mentors/CS_Course.csv deleted file mode 100644 index 85ef178e..00000000 --- a/compsocsite/mentors/CS_Course.csv +++ /dev/null @@ -1,10 +0,0 @@ -Computer Science I,CSCI,1100,"['M 4:00-4:50PM','M 5:00-5:50PM','M 6:00-6:50PM', 'T 10:00-11:50AM', 'T 12:00-1:50PM', 'T 2:00-3:50PM', 'T 4:00-4:50PM', 'T 5:00-5:50PM', 'T 6:00-6:50PM', 'W 10:00-11:50AM', 'W 12:00-1:50PM', 'W 2:00-3:50PM', 'W 4:00-5:50PM']" -Beginning Prog for Engineers,CSCI,1190,"['M 4:00-5:50PM', 'R 4:00-5:50PM', 'R 6:00-7:50PM']" -Data Structures,CSCI,1200,"['M 4:00-4:50PM', 'M 5:00-5:50PM', 'M 6:00-6:50PM', 'T 4:00-4:50PM', 'T 5:00-5:50PM', 'T 6:00-6:50PM', 'W 10:00-11:50AM', 'W 12:00-1:50PM', 'W 2:00-3:50PM', 'W 4:00-5:50PM', 'W 6:00-7:50PM']" -Foundations of Computer Science,CSCI,2200,"['W 10:00-10:50AM', 'W 11:00-11:50AM', 'W 12:00-12:50PM', 'W 1:00-1:50PM', 'W 4:00-4:50PM']" -Intro to Algorithms,CSCI,2300,"['W 10:00-11:50AM', 'W 12:00-1:50PM', 'W 2:00-3:50PM', 'W 4:00-5:50PM']" -Computer Organization,CSCI,2500,"['W 10:00-11:50AM', 'W 12:00-1:50PM', 'W 2:00-3:50PM']" -Principles of Software,CSCI,2600, -Intro to AI,CSCI,4150, -Operation Systems,CSCI,4210, -Database Systems,CSCI,4380, From 7d4569dc3d00456d94b642aeef5a922af0b04e9b Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 24 Nov 2019 22:51:54 -0500 Subject: [PATCH 38/44] Add junming to admin --- compsocsite/mentors/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compsocsite/mentors/views.py b/compsocsite/mentors/views.py index 8698fbaa..db53117e 100644 --- a/compsocsite/mentors/views.py +++ b/compsocsite/mentors/views.py @@ -629,7 +629,7 @@ def viewMatchResult(): def isMentorAdmin(request): - Admin_Email_List = ["zahavg@rpi.edu", "qianj2@rpi.edu", "cheny42@rpi.edu", "xial@rpi.edu", "hulbes@rpi.edu", "goldsd3@rpi.edu" ] + Admin_Email_List = ["tomjmwang@gmail.com", "zahavg@rpi.edu", "qianj2@rpi.edu", "cheny42@rpi.edu", "xial@rpi.edu", "hulbes@rpi.edu", "goldsd3@rpi.edu" ] if (request.user.email.strip() in Admin_Email_List): return True return False From a700b9bc09329a271a558fffe3f1beb0e12aea23 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Sun, 24 Nov 2019 23:30:33 -0500 Subject: [PATCH 39/44] Make new migrations --- .../appauth/migrations/0001_initial.py | 4 ++-- .../migrations/0002_auto_20191123_1035.py | 19 ------------------- .../mentors/migrations/0001_initial.py | 12 +++++++----- .../migrations/0002_course_time_slots.py | 18 ------------------ .../0003_mentor_mentored_non_cs_bf.py | 18 ------------------ 5 files changed, 9 insertions(+), 62 deletions(-) delete mode 100644 compsocsite/appauth/migrations/0002_auto_20191123_1035.py delete mode 100644 compsocsite/mentors/migrations/0002_course_time_slots.py delete mode 100644 compsocsite/mentors/migrations/0003_mentor_mentored_non_cs_bf.py diff --git a/compsocsite/appauth/migrations/0001_initial.py b/compsocsite/appauth/migrations/0001_initial.py index 667034b8..2b1173fd 100644 --- a/compsocsite/appauth/migrations/0001_initial.py +++ b/compsocsite/appauth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.1 on 2019-11-22 21:31 +# Generated by Django 2.2.1 on 2019-11-25 04:28 from django.conf import settings from django.db import migrations, models @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ('numq', models.IntegerField(default=0)), ('exp_data', models.TextField(default='{}')), ('mentor_applied', models.BooleanField(default=False)), - ('mentor_profile', models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='mentors.Mentor')), + ('mentor_profile', models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mentors.Mentor')), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), diff --git a/compsocsite/appauth/migrations/0002_auto_20191123_1035.py b/compsocsite/appauth/migrations/0002_auto_20191123_1035.py deleted file mode 100644 index 51e35823..00000000 --- a/compsocsite/appauth/migrations/0002_auto_20191123_1035.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-23 15:35 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('appauth', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='mentor_profile', - field=models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mentors.Mentor'), - ), - ] diff --git a/compsocsite/mentors/migrations/0001_initial.py b/compsocsite/mentors/migrations/0001_initial.py index 3c333a27..375d8cf7 100644 --- a/compsocsite/mentors/migrations/0001_initial.py +++ b/compsocsite/mentors/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.1 on 2019-11-22 21:31 +# Generated by Django 2.2.1 on 2019-11-25 04:28 import django.core.validators from django.db import migrations, models @@ -22,6 +22,7 @@ class Migration(migrations.Migration): ('number', models.CharField(default='1000', max_length=4)), ('name', models.CharField(default='none', max_length=50)), ('instructor', models.CharField(default='none', max_length=50)), + ('time_slots', models.CharField(default='[]', max_length=500)), ('feature_cumlative_GPA', models.IntegerField(default=0)), ('feature_has_taken', models.IntegerField(default=0)), ('feature_course_GPA', models.IntegerField(default=0)), @@ -51,15 +52,16 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('applied', models.BooleanField(default=False)), ('RIN', models.CharField(max_length=9, validators=[django.core.validators.MinLengthValidator(9)])), - ('first_name', models.CharField(max_length=50)), - ('last_name', models.CharField(max_length=50)), - ('GPA', mentors.models.MinMaxFloat()), + ('first_name', models.CharField(default='', max_length=50)), + ('last_name', models.CharField(default='', max_length=50)), + ('GPA', mentors.models.MinMaxFloat(default=0)), ('email', models.CharField(max_length=50)), - ('phone', models.CharField(max_length=50)), + ('phone', models.CharField(default='', max_length=10)), ('recommender', models.CharField(default='', max_length=50)), ('compensation', models.CharField(choices=[('n', 'No Preference'), ('p', 'Pay ($14/hour) '), ('c', 'Credit')], default='n', max_length=1)), ('studnet_status', models.CharField(choices=[('i', 'International'), ('d', 'Domestic')], default='i', max_length=1)), ('employed_paid_before', models.BooleanField(default=False)), + ('mentored_non_cs_bf', models.BooleanField(default=False)), ('time_slots', models.CharField(default='[]', max_length=1000)), ('other_times', models.CharField(default='', max_length=1000)), ('relevant_info', models.CharField(default='', max_length=1000)), diff --git a/compsocsite/mentors/migrations/0002_course_time_slots.py b/compsocsite/mentors/migrations/0002_course_time_slots.py deleted file mode 100644 index 3a6c5dd1..00000000 --- a/compsocsite/mentors/migrations/0002_course_time_slots.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-24 17:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='course', - name='time_slots', - field=models.CharField(default='[]', max_length=500), - ), - ] diff --git a/compsocsite/mentors/migrations/0003_mentor_mentored_non_cs_bf.py b/compsocsite/mentors/migrations/0003_mentor_mentored_non_cs_bf.py deleted file mode 100644 index d62cb009..00000000 --- a/compsocsite/mentors/migrations/0003_mentor_mentored_non_cs_bf.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-11-24 21:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('mentors', '0002_course_time_slots'), - ] - - operations = [ - migrations.AddField( - model_name='mentor', - name='mentored_non_cs_bf', - field=models.BooleanField(default=False), - ), - ] From 67f08058b7bbc47f0aad605f7e7df799172207eb Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Mon, 25 Nov 2019 01:49:25 -0500 Subject: [PATCH 40/44] Make another migrations --- .../appauth/migrations/0001_initial.py | 5 +--- .../migrations/0002_auto_20191125_0146.py | 25 +++++++++++++++++++ .../mentors/migrations/0001_initial.py | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 compsocsite/appauth/migrations/0002_auto_20191125_0146.py diff --git a/compsocsite/appauth/migrations/0001_initial.py b/compsocsite/appauth/migrations/0001_initial.py index 2b1173fd..dd7ef141 100644 --- a/compsocsite/appauth/migrations/0001_initial.py +++ b/compsocsite/appauth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.1 on 2019-11-25 04:28 +# Generated by Django 2.2 on 2019-11-25 06:45 from django.conf import settings from django.db import migrations, models @@ -10,7 +10,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('mentors', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -35,8 +34,6 @@ class Migration(migrations.Migration): ('finished', models.BooleanField(default=False)), ('numq', models.IntegerField(default=0)), ('exp_data', models.TextField(default='{}')), - ('mentor_applied', models.BooleanField(default=False)), - ('mentor_profile', models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mentors.Mentor')), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), diff --git a/compsocsite/appauth/migrations/0002_auto_20191125_0146.py b/compsocsite/appauth/migrations/0002_auto_20191125_0146.py new file mode 100644 index 00000000..7b8c5689 --- /dev/null +++ b/compsocsite/appauth/migrations/0002_auto_20191125_0146.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2 on 2019-11-25 06:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mentors', '0001_initial'), + ('appauth', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='mentor_applied', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='userprofile', + name='mentor_profile', + field=models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mentors.Mentor'), + ), + ] diff --git a/compsocsite/mentors/migrations/0001_initial.py b/compsocsite/mentors/migrations/0001_initial.py index 375d8cf7..50d85462 100644 --- a/compsocsite/mentors/migrations/0001_initial.py +++ b/compsocsite/mentors/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.1 on 2019-11-25 04:28 +# Generated by Django 2.2 on 2019-11-25 06:34 import django.core.validators from django.db import migrations, models From 015796b8d54823f9eb1028e4192f0942e2dc017e Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Mon, 25 Nov 2019 01:57:11 -0500 Subject: [PATCH 41/44] Migration change --- .../appauth/migrations/0001_initial.py | 20 ++--------- .../0002_userprofile_displaypref.py | 20 +++++++++++ .../migrations/0003_auto_20160702_1713.py | 35 +++++++++++++++++++ .../migrations/0004_userprofile_showhint.py | 20 +++++++++++ .../migrations/0005_auto_20170306_1628.py | 35 +++++++++++++++++++ .../migrations/0006_auto_20171117_1007.py | 25 +++++++++++++ .../migrations/0007_userprofile_code.py | 20 +++++++++++ .../migrations/0008_userprofile_comments.py | 20 +++++++++++ .../migrations/0009_auto_20171204_1448.py | 20 +++++++++++ .../migrations/0010_auto_20171216_1342.py | 25 +++++++++++++ .../migrations/0011_auto_20171224_2353.py | 27 ++++++++++++++ .../migrations/0012_auto_20171224_2356.py | 22 ++++++++++++ .../migrations/0013_auto_20171229_1132.py | 22 ++++++++++++ .../migrations/0014_auto_20171229_1135.py | 20 +++++++++++ .../migrations/0015_userprofile_finished.py | 20 +++++++++++ .../migrations/0016_userprofile_numq.py | 20 +++++++++++ .../migrations/0017_userprofile_exp_data.py | 20 +++++++++++ ...125_0146.py => 0018_auto_20191125_0156.py} | 4 +-- .../mentors/migrations/0001_initial.py | 2 +- 19 files changed, 377 insertions(+), 20 deletions(-) mode change 100644 => 100755 compsocsite/appauth/migrations/0001_initial.py create mode 100755 compsocsite/appauth/migrations/0002_userprofile_displaypref.py create mode 100755 compsocsite/appauth/migrations/0003_auto_20160702_1713.py create mode 100755 compsocsite/appauth/migrations/0004_userprofile_showhint.py create mode 100755 compsocsite/appauth/migrations/0005_auto_20170306_1628.py create mode 100755 compsocsite/appauth/migrations/0006_auto_20171117_1007.py create mode 100755 compsocsite/appauth/migrations/0007_userprofile_code.py create mode 100755 compsocsite/appauth/migrations/0008_userprofile_comments.py create mode 100755 compsocsite/appauth/migrations/0009_auto_20171204_1448.py create mode 100755 compsocsite/appauth/migrations/0010_auto_20171216_1342.py create mode 100755 compsocsite/appauth/migrations/0011_auto_20171224_2353.py create mode 100755 compsocsite/appauth/migrations/0012_auto_20171224_2356.py create mode 100755 compsocsite/appauth/migrations/0013_auto_20171229_1132.py create mode 100755 compsocsite/appauth/migrations/0014_auto_20171229_1135.py create mode 100755 compsocsite/appauth/migrations/0015_userprofile_finished.py create mode 100755 compsocsite/appauth/migrations/0016_userprofile_numq.py create mode 100755 compsocsite/appauth/migrations/0017_userprofile_exp_data.py rename compsocsite/appauth/migrations/{0002_auto_20191125_0146.py => 0018_auto_20191125_0156.py} (86%) diff --git a/compsocsite/appauth/migrations/0001_initial.py b/compsocsite/appauth/migrations/0001_initial.py old mode 100644 new mode 100755 index dd7ef141..cab0b287 --- a/compsocsite/appauth/migrations/0001_initial.py +++ b/compsocsite/appauth/migrations/0001_initial.py @@ -1,4 +1,6 @@ -# Generated by Django 2.2 on 2019-11-25 06:45 +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2016-06-08 21:00 +from __future__ import unicode_literals from django.conf import settings from django.db import migrations, models @@ -18,22 +20,6 @@ class Migration(migrations.Migration): name='UserProfile', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('time_creation', models.DateTimeField()), - ('displayPref', models.IntegerField(default=1)), - ('emailInvite', models.BooleanField(default=False)), - ('emailDelete', models.BooleanField(default=False)), - ('emailStart', models.BooleanField(default=False)), - ('emailStop', models.BooleanField(default=False)), - ('showHint', models.BooleanField(default=True)), - ('mturk', models.IntegerField(default=0)), - ('age', models.IntegerField(default=0)), - ('code', models.CharField(blank=True, max_length=100, null=True)), - ('comments', models.CharField(blank=True, max_length=1000, null=True)), - ('sequence', models.TextField(default='')), - ('cur_poll', models.IntegerField(default=1)), - ('finished', models.BooleanField(default=False)), - ('numq', models.IntegerField(default=0)), - ('exp_data', models.TextField(default='{}')), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), diff --git a/compsocsite/appauth/migrations/0002_userprofile_displaypref.py b/compsocsite/appauth/migrations/0002_userprofile_displaypref.py new file mode 100755 index 00000000..1452c3e6 --- /dev/null +++ b/compsocsite/appauth/migrations/0002_userprofile_displaypref.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2016-06-20 20:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='displayPref', + field=models.IntegerField(default=1), + ), + ] diff --git a/compsocsite/appauth/migrations/0003_auto_20160702_1713.py b/compsocsite/appauth/migrations/0003_auto_20160702_1713.py new file mode 100755 index 00000000..012d8291 --- /dev/null +++ b/compsocsite/appauth/migrations/0003_auto_20160702_1713.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-02 21:13 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0002_userprofile_displaypref'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='emailDelete', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='userprofile', + name='emailInvite', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='userprofile', + name='emailStart', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='userprofile', + name='emailStop', + field=models.BooleanField(default=True), + ), + ] diff --git a/compsocsite/appauth/migrations/0004_userprofile_showhint.py b/compsocsite/appauth/migrations/0004_userprofile_showhint.py new file mode 100755 index 00000000..6dfb7520 --- /dev/null +++ b/compsocsite/appauth/migrations/0004_userprofile_showhint.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2016-08-09 00:19 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0003_auto_20160702_1713'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='showHint', + field=models.BooleanField(default=True), + ), + ] diff --git a/compsocsite/appauth/migrations/0005_auto_20170306_1628.py b/compsocsite/appauth/migrations/0005_auto_20170306_1628.py new file mode 100755 index 00000000..5466f063 --- /dev/null +++ b/compsocsite/appauth/migrations/0005_auto_20170306_1628.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-03-06 21:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0004_userprofile_showhint'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='emailDelete', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='userprofile', + name='emailInvite', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='userprofile', + name='emailStart', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='userprofile', + name='emailStop', + field=models.BooleanField(default=False), + ), + ] diff --git a/compsocsite/appauth/migrations/0006_auto_20171117_1007.py b/compsocsite/appauth/migrations/0006_auto_20171117_1007.py new file mode 100755 index 00000000..208699e6 --- /dev/null +++ b/compsocsite/appauth/migrations/0006_auto_20171117_1007.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-11-17 15:07 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0005_auto_20170306_1628'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='age', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='userprofile', + name='mturk', + field=models.IntegerField(default=0), + ), + ] diff --git a/compsocsite/appauth/migrations/0007_userprofile_code.py b/compsocsite/appauth/migrations/0007_userprofile_code.py new file mode 100755 index 00000000..70f3ac67 --- /dev/null +++ b/compsocsite/appauth/migrations/0007_userprofile_code.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-11-30 01:07 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0006_auto_20171117_1007'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='code', + field=models.IntegerField(default=0), + ), + ] diff --git a/compsocsite/appauth/migrations/0008_userprofile_comments.py b/compsocsite/appauth/migrations/0008_userprofile_comments.py new file mode 100755 index 00000000..af73f533 --- /dev/null +++ b/compsocsite/appauth/migrations/0008_userprofile_comments.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-12-01 03:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0007_userprofile_code'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='comments', + field=models.CharField(blank=True, max_length=1000, null=True), + ), + ] diff --git a/compsocsite/appauth/migrations/0009_auto_20171204_1448.py b/compsocsite/appauth/migrations/0009_auto_20171204_1448.py new file mode 100755 index 00000000..a2998aab --- /dev/null +++ b/compsocsite/appauth/migrations/0009_auto_20171204_1448.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-12-04 19:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0008_userprofile_comments'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='code', + field=models.CharField(blank=True, max_length=100, null=True), + ), + ] diff --git a/compsocsite/appauth/migrations/0010_auto_20171216_1342.py b/compsocsite/appauth/migrations/0010_auto_20171216_1342.py new file mode 100755 index 00000000..97ee3622 --- /dev/null +++ b/compsocsite/appauth/migrations/0010_auto_20171216_1342.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-12-16 18:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0009_auto_20171204_1448'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='cur_poll', + field=models.IntegerField(default=369), + ), + migrations.AddField( + model_name='userprofile', + name='sequence', + field=models.TextField(default=''), + ), + ] diff --git a/compsocsite/appauth/migrations/0011_auto_20171224_2353.py b/compsocsite/appauth/migrations/0011_auto_20171224_2353.py new file mode 100755 index 00000000..30067387 --- /dev/null +++ b/compsocsite/appauth/migrations/0011_auto_20171224_2353.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-12-25 04:53 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0010_auto_20171216_1342'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='time_creation', + field=models.DateTimeField(default=datetime.datetime(2017, 12, 25, 4, 52, 59, 194669, tzinfo=utc), verbose_name='time_created'), + ), + migrations.AlterField( + model_name='userprofile', + name='cur_poll', + field=models.IntegerField(default=1), + ), + ] diff --git a/compsocsite/appauth/migrations/0012_auto_20171224_2356.py b/compsocsite/appauth/migrations/0012_auto_20171224_2356.py new file mode 100755 index 00000000..30a98ac4 --- /dev/null +++ b/compsocsite/appauth/migrations/0012_auto_20171224_2356.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-12-25 04:56 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0011_auto_20171224_2353'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='time_creation', + field=models.DateTimeField(verbose_name=datetime.datetime(2017, 12, 25, 4, 56, 19, 179450, tzinfo=utc)), + ), + ] diff --git a/compsocsite/appauth/migrations/0013_auto_20171229_1132.py b/compsocsite/appauth/migrations/0013_auto_20171229_1132.py new file mode 100755 index 00000000..59c0cd46 --- /dev/null +++ b/compsocsite/appauth/migrations/0013_auto_20171229_1132.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-12-29 16:32 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0012_auto_20171224_2356'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='time_creation', + field=models.DateTimeField(verbose_name=datetime.datetime(2017, 12, 29, 16, 32, 26, 643080, tzinfo=utc)), + ), + ] diff --git a/compsocsite/appauth/migrations/0014_auto_20171229_1135.py b/compsocsite/appauth/migrations/0014_auto_20171229_1135.py new file mode 100755 index 00000000..eb0c6fe4 --- /dev/null +++ b/compsocsite/appauth/migrations/0014_auto_20171229_1135.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-12-29 16:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0013_auto_20171229_1132'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='time_creation', + field=models.DateTimeField(), + ), + ] diff --git a/compsocsite/appauth/migrations/0015_userprofile_finished.py b/compsocsite/appauth/migrations/0015_userprofile_finished.py new file mode 100755 index 00000000..287b8995 --- /dev/null +++ b/compsocsite/appauth/migrations/0015_userprofile_finished.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-12-30 19:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0014_auto_20171229_1135'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='finished', + field=models.BooleanField(default=False), + ), + ] diff --git a/compsocsite/appauth/migrations/0016_userprofile_numq.py b/compsocsite/appauth/migrations/0016_userprofile_numq.py new file mode 100755 index 00000000..11528e62 --- /dev/null +++ b/compsocsite/appauth/migrations/0016_userprofile_numq.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-01-06 16:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0015_userprofile_finished'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='numq', + field=models.IntegerField(default=0), + ), + ] diff --git a/compsocsite/appauth/migrations/0017_userprofile_exp_data.py b/compsocsite/appauth/migrations/0017_userprofile_exp_data.py new file mode 100755 index 00000000..a126beb1 --- /dev/null +++ b/compsocsite/appauth/migrations/0017_userprofile_exp_data.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2018-01-24 07:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appauth', '0016_userprofile_numq'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='exp_data', + field=models.TextField(default='{}'), + ), + ] diff --git a/compsocsite/appauth/migrations/0002_auto_20191125_0146.py b/compsocsite/appauth/migrations/0018_auto_20191125_0156.py similarity index 86% rename from compsocsite/appauth/migrations/0002_auto_20191125_0146.py rename to compsocsite/appauth/migrations/0018_auto_20191125_0156.py index 7b8c5689..dbfb372a 100644 --- a/compsocsite/appauth/migrations/0002_auto_20191125_0146.py +++ b/compsocsite/appauth/migrations/0018_auto_20191125_0156.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2 on 2019-11-25 06:46 +# Generated by Django 2.2 on 2019-11-25 06:56 from django.db import migrations, models import django.db.models.deletion @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ ('mentors', '0001_initial'), - ('appauth', '0001_initial'), + ('appauth', '0017_userprofile_exp_data'), ] operations = [ diff --git a/compsocsite/mentors/migrations/0001_initial.py b/compsocsite/mentors/migrations/0001_initial.py index 50d85462..c0cfc664 100644 --- a/compsocsite/mentors/migrations/0001_initial.py +++ b/compsocsite/mentors/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2 on 2019-11-25 06:34 +# Generated by Django 2.2 on 2019-11-25 06:56 import django.core.validators from django.db import migrations, models From f19cff764ebb779e29252780bcd5db506883a952 Mon Sep 17 00:00:00 2001 From: 1 <1> Date: Mon, 25 Nov 2019 11:12:42 -0500 Subject: [PATCH 42/44] change admin --- compsocsite/mentors/templates/mentors/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compsocsite/mentors/templates/mentors/index.html b/compsocsite/mentors/templates/mentors/index.html index 9aac3d61..512ed7f7 100755 --- a/compsocsite/mentors/templates/mentors/index.html +++ b/compsocsite/mentors/templates/mentors/index.html @@ -16,7 +16,6 @@

            -{% if admin %}
            @@ -102,6 +101,8 @@
            +{% if admin %} +
            From 5a37ef9bc5c63b3ab9b81fd6ebe53cd8246ffd25 Mon Sep 17 00:00:00 2001 From: starwarswii Date: Tue, 17 Dec 2019 23:08:11 -0500 Subject: [PATCH 43/44] updated match.py to fix bug now students should not be matched to things they don't want at all --- compsocsite/mentors/match.py | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/compsocsite/mentors/match.py b/compsocsite/mentors/match.py index 19843def..f23f595b 100755 --- a/compsocsite/mentors/match.py +++ b/compsocsite/mentors/match.py @@ -253,39 +253,39 @@ def __len__(self): next[c] = self.after(c, s) - #if s is already matched - if s in studentMatching: + #the class s is currently matched to, or None if unmatched + c2 = None - #current class that s is in + if s in studentMatching: c2 = studentMatching[s] assert c2 != c - #if s prefers c more than current class - if self.prefers(s, c, c2): + + #we propose to c + #if s prefers c more than the current class / being unmatched + if self.prefers(s, c, c2): + + #we will move s from c2 to c + #we don't need to do this if c2 was none, + #meaning s was unmatched + if c2 != None: - #old class c2 becomes available again - #unmatch to old class currCap[c2] -= 1 #if c2 isn't already scheduled to match, put it in the queue if c2 not in queue: queue.put(c2) - - #s becomes matched to c - studentMatching[s] = c - currCap[c] += 1 - - - #otherwise, we're rejected - #just go to the next proposal - - #else, s is unmatched, so c gets them - else: + + #s becomes matched to c studentMatching[s] = c currCap[c] += 1 + + + #otherwise, we're rejected + #just go to the next proposal #we finished the proposals for this class for this round @@ -336,4 +336,4 @@ def isStable(self, studentMatching=None, verbose=False): if verbose: print(f"{c}'s marriage to {s} is unstable:\n{c} prefers {p} over {s} and {p} prefers {c} over her current husband {c2}") return False - return True \ No newline at end of file + return True From 86ce88c7219d92e321cd9aa3d0bc2bf631e4b90f Mon Sep 17 00:00:00 2001 From: starwarswii Date: Tue, 17 Dec 2019 23:29:33 -0500 Subject: [PATCH 44/44] removed match_change.py was an old copy of the alg. --- compsocsite/mentors/match_change.py | 370 ---------------------------- 1 file changed, 370 deletions(-) delete mode 100755 compsocsite/mentors/match_change.py diff --git a/compsocsite/mentors/match_change.py b/compsocsite/mentors/match_change.py deleted file mode 100755 index b90806f4..00000000 --- a/compsocsite/mentors/match_change.py +++ /dev/null @@ -1,370 +0,0 @@ -from collections import defaultdict, deque - -class Matcher: - - #constructs a Matcher instance - - #studentPrefs is a dict from student to class ranking as a list - #e.g. "student1" -> ["class1", "class3", "class2"] - #the ranking doesn't need to be complete - #all missing classes are treated as "worse than nothing" - - #studentFeatures is a dict from student to feature vector - #e.g "student1" -> (0, 1, 3.8) - #the feature vector gives a score for each feature for this student - - #classCaps is a dict from class to maximum capacity - #e.g. "class1" -> 5 - - #classFeatures is a dict from class to feature vector - #e.g. "class1" -> (98, 70, 65) - - #the dot product of a student's feature vector with a particuar class's feature vector is its score - #in terms of that class - def __init__(self, studentPrefs, studentFeatures, classCaps, classFeatures): - - self.studentPrefs = studentPrefs - self.classCaps = classCaps - self.studentFeatures = studentFeatures - self.classFeatures = classFeatures - ''' - #dot product of a and b - def dot(a, b): - #assert len(a) == len(b) - return sum(x*y for x, y in zip(a, b)) - - #returns a complete ranking over students for one specific class - #classFeature is the feature vector for a class - - def makeClassPref(classFeature): - - #return list of students, sorted by dot product, then student name (for tiebreaking) - return sorted(studentPrefs.keys(), key=lambda x: (dot(classFeature, studentFeatures[x]), x), reverse=True) - - #call makeClassPref on each classFeature vector to make the preferences - self.classPrefs = {c: makeClassPref(f) for c, f in classFeatures.items()} - ''' - - #output variables - self.studentMatching = {} #student -> class - self.classMatching = defaultdict(list) #class -> [students] - - #we index preferences at initialization to avoid expensive lookups when matching - self.classRank = defaultdict(dict) #classRank[c][s] is c's ranking of s - self.studentRank = defaultdict(dict) #studentRank[s][c] is s's ranking of c - - #if ranking isn't present, treated as less than none - ''' - for c, prefs in self.classPrefs.items(): - for i, s in enumerate(prefs): - self.classRank[c][s] = i - - for s, prefs in studentPrefs.items(): - for i, c in enumerate(prefs): - self.studentRank[s][c] = i - ''' - - - #Test whether s prefers c over c2. - def prefers(self, s, c, c2): - ranking = self.studentRank[s] - - if c in ranking: - - if c2 in ranking: - #both in ranking - - #return normally - return ranking[c] < ranking[c2] - - else: - #c in ranking, but c2 not in ranking - - #c is preferred - return True - - else: - if c2 in ranking: - #c not in ranking, but c2 in ranking - - #c2 is preferred - return False - - else: - #both not in ranking - - #none, none: false - #strr, none: false - #none, strr: true - #strr, strr: c < c2 - - #None represents no one. it can be thought of as being after the last ranked element, - #and before the unranked ones - - if c2 == None: - #either c is str and c2 is none, or both none - #either way, c is not preferred over c2 - return False - - if c == None: - #c is none and c2 is str - #so c, being no one, is preferred over unranked c2 - return True - - - #if we get here, none isn't involved - #both c and c2 are unranked str's - - #preference based on alphabetical order - return c < c2 - - - #Return the student favored by c after s. - def after(self, c, s): - - #TODO extra checking here might not be needed as classes have full ranking - - if s not in self.classRank[c]: - #TODO will this happen? probably not - print(f"student {s} is not in {c}'s ranking") - #assert False - - #in case it does happen, - #we return the next alphabetical student who is also not ranked - #it is expensive though - - students = sorted(self.studentPrefs.keys()) - - #if we're trying to find the next student after "no one", - #we search from the beginning - if s == None: - i = 0 - - else: - i = students.index(s)+1 - - while i < len(students) and students[i] in self.classRank[c]: - i += 1 - - - if i >= len(students): - #this alg in't perfect. we're mixing what None means - #none usually means "no one" - #but here it means there isn't a next student - #this probably doesn't matter much, as this code will probably not be used - return None - - print(f"using {students[i]}") - return students[i] - - #index of student following s in list of prefs - i = self.classRank[c][s] + 1 - - if i >= len(self.classPrefs[c]): - #no other students are prefered. - return None - - return self.classPrefs[c][i] - - - - #Try to match all classes with their next preferred spouse. - #does class-proposing Gale-Shapely - def match(self): - def dot(a, b): - #assert len(a) == len(b) - return sum(x*y for x, y in zip(a, b)) - - f = True; - studentMatched = {} - for oneStudent in self.studentFeatures: - studentMatched[oneStudent] = False; - - classMatching = {} - for oneClass in self.classFeatures: - classMatching[oneClass] = [] - - while f: - for oneStudent in studentMatched: - if studentMatched[oneStudent]: - continue - if self.studentPrefs[oneStudent] == []: - continue - oneClass = self.studentPrefs[oneStudent].pop(0) - classMatching[oneClass].append(oneStudent) - if len(classMatching[oneClass]) > self.classCaps[oneClass]: - classMatching[oneClass].sort(key=lambda x: dot(self.classFeatures[oneClass], self.studentFeatures[x][oneClass]), reverse=True) - popStudent = classMatching[oneClass].pop(-1) - studentMatched[popStudent] = True - - - # Check if all the classes are full. - f = False; - for oneClass in classMatching: - if len(classMatching[oneClass]) < self.classCaps[oneClass]: - f = True - break - for oneClass in classMatching: - classMatching[oneClass].sort(key=lambda x: dot(self.classFeatures[oneClass], self.studentFeatures[x][oneClass]), reverse=True) - - ''' - #simple combination of a queue with a set - #the set is used for quick "contains" lookups - class SmartQueue: - - def __init__(self): - self.queue = deque() - self.set = set() - - def put(self, x): - self.queue.append(x) - self.set.add(x) - - def pop(self): - x = self.queue.popleft() - self.set.remove(x) - return x - - def __contains__(self, x): - return x in self.set - - def __len__(self): - return len(self.queue) - - - #classes (full ranking) - #students (partial ranking) - - #queue of classes we still have to match - queue = SmartQueue() - - #next is a map from class to next student to propose to - #starts as first preferences - next = {} - - #mapping from students to current class - studentMatching = {} - - #the current capacity for each class - currCap = defaultdict(int) - - #initalize - #we do this in a loop so we only iterate over self.classPrefs once - for c, rank in self.classPrefs.items(): - #we start with all the classes in the queue - queue.put(c) - - #and all the classes will propose to their top ranking student - next[c] = rank[0] - - - #while we still have classes to match - while len(queue) > 0: - - #take class off list - c = queue.pop() - - #make proposals for the remaining capacity - for i in range(self.classCaps[c] - currCap[c]): - - #next student for c to propose to - s = next[c] - - #if we run out of students to propose to - if s == None: - #we break, finished matching, but having less than max capacity - break - - #student after s in c's list of prefs - #"next-next" student for c to propose to - next[c] = self.after(c, s) - - - #if s is already matched - if s in studentMatching: - - #current class that s is in - c2 = studentMatching[s] - - assert c2 != c - - #if s prefers c more than current class - if self.prefers(s, c, c2): - - #old class c2 becomes available again - - #unmatch to old class - currCap[c2] -= 1 - - #if c2 isn't already scheduled to match, put it in the queue - if c2 not in queue: - queue.put(c2) - - #s becomes matched to c - studentMatching[s] = c - currCap[c] += 1 - - - #otherwise, we're rejected - #just go to the next proposal - - #else, s is unmatched, so c gets them - else: - #s becomes matched to c - studentMatching[s] = c - currCap[c] += 1 - - - #we finished the proposals for this class for this round - #now, does this class need another round? - - #if we aren't full, and haven't been "none'd", we re-add ourself - #"none'd" meaning we ran out of students to propose to - if currCap[c] < self.classCaps[c] and next[c] != None: - queue.put(c) - ''' - - #now we've matched all classes, so we're done - - #populate studentMatching - ''' - self.studentMatching = studentMatched - - #populate classMatching from studentMatching - for s, c in studentMatched.items(): - self.classMatching[c].append(s) - ''' - self.classMatching = classMatching - - return self.classMatching - - #check if the mapping of studentMatching to husbands is stable - #TODO this doesn't look at unmatched students or classes. is that a problem? - ''' - def isStable(self, studentMatching=None, verbose=False): - - if studentMatching is None: - studentMatching = self.studentMatching - - for s, c in studentMatching.items(): - - i = self.classRank[c][s] - - preferred = self.classPrefs[c][:i] - - for p in preferred: - - #it's possible p is unmatched - #in that case, c2 is None - c2 = None - if p in studentMatching: - c2 = studentMatching[p] - - #check if p prefers us over current matching - #if c2 is none, this just checks if p prefers us to nobody - if self.prefers(p, c, c2): - if verbose: - print(f"{c}'s marriage to {s} is unstable:\n{c} prefers {p} over {s} and {p} prefers {c} over her current husband {c2}") - return False - return True - '''