diff --git a/.htaccess b/.htaccess
deleted file mode 100644
index 7a1a452..0000000
--- a/.htaccess
+++ /dev/null
@@ -1,8 +0,0 @@
-Options +ExecCGI -Indexes
-AddHandler cgi-script .py
-
-RewriteEngine on
-RewriteCond %{HTTP_HOST} ^192.168.*
-RewriteRule ^your_schedule\.pdf$ /~tdimson/coursequalifier.com/makePDFCalendarCGI.py [L,NC]
-
-RewriteRule ^your_schedule\.pdf$ makePDFCalendarCGI.py
diff --git a/coursequalifier/config/deployment.ini_tmpl b/coursequalifier/config/deployment.ini_tmpl
index 5e3f7df..1017892 100644
--- a/coursequalifier/config/deployment.ini_tmpl
+++ b/coursequalifier/config/deployment.ini_tmpl
@@ -25,6 +25,7 @@ beaker.session.secret = ${app_instance_secret}
app_instance_uuid = ${app_instance_uuid}
uwdata.address = uwdata.ca
uwdata.key = GET_YOUR_OWN
+maxSearchSpace = 10000
# If you'd like to fine-tune the individual locations of the cache data dirs
# for the Cache data, or the Session saves, un-comment the desired settings
diff --git a/coursequalifier/controllers/schedule.py b/coursequalifier/controllers/schedule.py
index 98d84ee..8749fd4 100644
--- a/coursequalifier/controllers/schedule.py
+++ b/coursequalifier/controllers/schedule.py
@@ -4,9 +4,10 @@
import logging
from itertools import chain
-from pylons import request, response, session, tmpl_context as c
+from pylons import request, response, session, tmpl_context as c, config
from pylons.controllers.util import abort, redirect_to
+from coursequalifier.lib.uwdata import CourseMissingException, UWDataError
from coursequalifier.lib.base import BaseController, render
from coursequalifier.lib.pdf_schedule import PDFSchedule
from coursequalifier.lib.filters import NotFullFilter, \
@@ -31,38 +32,66 @@ def compute_all(self):
directCourses = []
for course in r['courses']:
query = course["course_query"].strip()
- if query == "":
- continue
+ if query == "": continue
courseFilters = self._course_filters(course["options"])
m = self.directRE.match(query)
if m:
- courses = Course.coursesFromCode(m.group(1), m.group(2), term,\
- sectionFilters=requestSectionFilters)
+ try:
+ courses = Course.coursesFromCode(m.group(1), m.group(2), term,\
+ sectionFilters=requestSectionFilters)
+ except CourseMissingException, e:
+ return json.dumps({"error": {
+ "type": "no_query_results",
+ "query": query
+ }})
+ except UWDataError, e:
+ return json.dumps({"error": {
+ "type": "uwdata",
+ "query": query
+ }})
catalogFilters.extend(self._course_catalog_filters(courses, course["options"]))
directCourses.extend([e for e in courses\
if all(courseFilter.passes(e) for courseFilter in courseFilters)])
else:
courseGroups = []
- for courseGroup in Course.courseGroupsFromSearch(query, term, sectionFilters=requestSectionFilters):
- filteredGroup = [e for e in courseGroup if all(courseFilter.passes(e) for courseFilter in courseFilters)]
- if len(filteredGroup) > 0:
- courseGroups.append(filteredGroup)
+ try:
+ for courseGroup in Course.courseGroupsFromSearch(query, term, sectionFilters=requestSectionFilters):
+ filteredGroup = [e for e in courseGroup if all(courseFilter.passes(e) for courseFilter in courseFilters)]
+ if len(filteredGroup) > 0:
+ courseGroups.append(filteredGroup)
+ except CourseMissingException, e:
+ return json.dumps({"error": {
+ "type": "no_query_results",
+ "query": query
+ }})
+ except UWDataError, e:
+ return json.dumps({"error": {
+ "type": "uwdata",
+ "query": query
+ }})
catalogFilters.extend(self._course_catalog_filters(chain(*courseGroups), course["options"]))
searchGroups.append(courseGroups)
- allCourses = directCourses + list(chain(*([chain(*e) for e in searchGroups])))
+ allCourses = directCourses + list(chain(*([chain(*e) for e in searchGroups])))
+ searchSpaceCount = Catalog.searchSpaceCount(directCourses, searchGroups)
+ if searchSpaceCount > int(config['maxSearchSpace']):
+ return json.dumps({"error": {
+ "type": "large_search_space",
+ "size": searchSpaceCount
+ }})
- catalogs = [c for c in Catalog.computeAll(directCourses, searchGroups)\
- if all(catalogFilter.passes(c) for catalogFilter in catalogFilters)]
+ catalogs, conflicts = Catalog.computeAll(directCourses, searchGroups)
+ filteredCatalogs= [c for c in catalogs \
+ if all(catalogFilter.passes(c) for catalogFilter in catalogFilters)]
return json.dumps({"result": {
- "courses": dict((e.uniqueName, self._course_dict(e)) for e in allCourses),
- "conflicts": {"courses": [], "messages": []},
- "catalogs": [self._catalog_dict(c) for c in catalogs]
+ "courses": dict((e.uniqueName, self._course_dict(e)) for e in allCourses),
+ "conflicts": [self._conflict_dict(c) for c in conflicts],
+ "catalogs": [self._catalog_dict(c) for c in filteredCatalogs]
}})
def pdf(self):
@@ -125,6 +154,12 @@ def _request_section_filters(self, req):
return ret
+ def _conflict_dict(self, conflict):
+ s1,s2 = conflict
+ return [{ "courseName": s.courseName,
+ "sectionNum": s.sectionNum
+ } for s in (s1,s2)]
+
def _course_dict(self, course):
return {
diff --git a/coursequalifier/lib/filters.py b/coursequalifier/lib/filters.py
index 67c619e..9cf4ec2 100644
--- a/coursequalifier/lib/filters.py
+++ b/coursequalifier/lib/filters.py
@@ -59,7 +59,5 @@ def __init__(self, courseGroup, requiredSectionNumbers=set()):
def passes(self, catalog):
scopedNums = set(e.sectionNum for e in catalog.sections if e.courseName in self.courseNames)
- print scopedNums
- print self.requiredSectionNumbers
return len(self.requiredSectionNumbers - scopedNums) == 0
diff --git a/coursequalifier/lib/uwdata.py b/coursequalifier/lib/uwdata.py
index 824e385..15119e7 100644
--- a/coursequalifier/lib/uwdata.py
+++ b/coursequalifier/lib/uwdata.py
@@ -24,7 +24,6 @@ def pullSearchCourses(query):
)
def getJSONFromPath(basePath, query=[]):
- print config['uwdata.address']
connection = HTTPConnection(config['uwdata.address'])
path = "%s?key=%s%s" % \
@@ -43,6 +42,8 @@ def getJSONFromPath(basePath, query=[]):
text = data['error']['text']
if text == "Unknown course":
raise CourseMissingException()
+ elif text == "No courses found":
+ raise CourseMissingException()
else:
raise UWDataError(data['error']['text'])
else:
diff --git a/coursequalifier/model/catalog.py b/coursequalifier/model/catalog.py
index dfec2a4..8632649 100755
--- a/coursequalifier/model/catalog.py
+++ b/coursequalifier/model/catalog.py
@@ -13,9 +13,10 @@ def cartesian(*args):
class CatalogCombinatorics(object):
"""Algorithm-object for doing actual 'course qualifying'."""
def __init__(self, direct, searchGroups):
- self.direct = direct
- self.searchGroups = searchGroups
- self.output = []
+ self.direct = direct
+ self.searchGroups = searchGroups
+ self.output = []
+ self.conflictingSections = []
def computeSections(self):
self.computeInternal(self.direct)
@@ -42,24 +43,40 @@ def computeSearchCourses(self):
def computeInternal(self, remainingCourses, sectionAcc=[]):
if len(remainingCourses) == 0:
- if len(sectionAcc) > 0: #and self.checkCatalog(sectionAcc):
- self.output.append(sectionAcc)
+ if len(sectionAcc) > 0:
+ self.output.append(sectionAcc) #Side effect!
else:
currentCourse = remainingCourses.pop()
for currSection in currentCourse.sections:
for otherSection in sectionAcc:
if currSection.conflictsWith(otherSection):
- #self.conflictingSections.add((currSection, otherSection))
+ self.conflictingSections.append((currSection, otherSection))
break
else:
self.computeInternal(remainingCourses[:], sectionAcc[:] + [currSection])
class Catalog(object):
+ @classmethod
+ def searchSpaceCount(cls, directCourses, searchGroups):
+ if len(directCourses) == 0 and len(searchGroups) == 0:
+ return 0
+
+ if len(directCourses) > 0:
+ numCatalogs = reduce(lambda x,y: x * y, (len(e.sections) \
+ for e in directCourses if len(e.sections) > 0))
+ else:
+ numCatalogs = 1
+
+ for courseOption in searchGroups:
+ numClasses *= sum(sum(len(c.sections) for c in courseGroup) for courseGroup in searchGroups)
+
+ return numCatalogs
+
@classmethod
def computeAll(cls, directCourses, searchGroups):
c = CatalogCombinatorics(directCourses, searchGroups)
c.computeSections()
- return [cls(e) for e in c.output]
+ return ([cls(e) for e in c.output], c.conflictingSections)
def __init__(self, sections):
self.sections = sections
diff --git a/coursequalifier/public/css/qualifier.css b/coursequalifier/public/css/qualifier.css
index 141a6d2..e808a4e 100755
--- a/coursequalifier/public/css/qualifier.css
+++ b/coursequalifier/public/css/qualifier.css
@@ -1,7 +1,7 @@
body {
text-align:center;
- font-family:Arial, Helvetica, sans-serif;
- font-size:0.9em;
+ font-family: Verdana, Arial, sans-serif;
+ font-size:75%;
margin:0;
padding:0;
background-color:#646890;
@@ -69,6 +69,22 @@ html .fb_share_button:hover { color:#a0a0a0; margin-left: 10px; border-color:#29
margin-top: 1em;
}
+#error_area {
+ border: 1px solid black;
+ padding: 10px 12px;
+ background: #800000;
+ width: 80%;
+ color: white;
+ display: none;
+}
+
+#error_area h3 {
+ font-size: 18pt;
+ font-weight: bold;
+ margin-top: 0;
+ margin-bottom: 10px;
+}
+
#too_many_explanation {
border: 1px solid #c93;
padding: 10px 12px;
@@ -91,6 +107,10 @@ html .fb_share_button:hover { color:#a0a0a0; margin-left: 10px; border-color:#29
#too_many_number {
}
+#result_area {
+ display: none;
+}
+
h2 {
color: #0080c3;
font-size: 1.5em;
@@ -207,7 +227,7 @@ a, a:visited, a:active {
}
span.noemphasis {
- font-size: 0.7em;
+ font-size: 0.8em;
}
diff --git a/coursequalifier/public/favicon.ico b/coursequalifier/public/favicon.ico
deleted file mode 100644
index 21e215e..0000000
Binary files a/coursequalifier/public/favicon.ico and /dev/null differ
diff --git a/coursequalifier/public/js/qualifier.js b/coursequalifier/public/js/qualifier.js
index cfe38ef..a4e21bf 100755
--- a/coursequalifier/public/js/qualifier.js
+++ b/coursequalifier/public/js/qualifier.js
@@ -296,15 +296,12 @@ function getCoursesObject() {
var courses = [];
var course_nodes = Dom.getElementsByClassName( "course", null, course_form );
- for( var i = 0; i < course_nodes.length; i++ )
- {
+ for(var i = 0; i < course_nodes.length; i++) {
courseObject = serializeFromRoot( course_nodes[i], true );
optionsSpan = Dom.getChildrenBy( course_nodes[i], function(e) { return e.className == "options"; } )[0]
courseObject["options"] = serializeFromRoot( optionsSpan, false );
-
courses.push( courseObject );
}
-
return courses;
}
@@ -345,11 +342,9 @@ function serializeFromRoot(root, first_level) {
var select = selectElements[i];
var selectChildren = select.getElementsByTagName( "option" );
- for( var j =0;j < selectChildren.length; j++ )
- {
+ for(var j =0;j < selectChildren.length; j++) {
var option = selectChildren[j];
- if( option.selected )
- {
+ if(option.selected) {
options[ select.name ] = option.value;
}
}
@@ -373,18 +368,16 @@ function submitCourses(e) {
"courses": getCoursesObject()
};
- document.getElementById("error_area").innerHTML = "";
- document.getElementById("info_area").innerHTML = "";
+ document.getElementById("error_area").style.display = "none";
+ document.getElementById("trace_area").style.display = "none";
+ document.getElementById("result_area").style.display = "none";
document.getElementById("too_many_explanation").style.display = "none";
- document.getElementById( "row_select" ).style.display = "none";
- document.getElementById( "show_conflicts" ).innerHTML = "";
- document.getElementById( "show_conflicts_number" ).innerHTML = "";
+ document.getElementById("row_select").style.display = "none";
+ document.getElementById("show_conflicts").innerHTML = "";
+ document.getElementById("show_conflicts_number").innerHTML = "";
showLoadingDialog();
hideConflicts();
- hideCalendar();
- hideCatalogInformation();
- hideQualifierGrid();
var pdfDiv = document.getElementById("create_pdf_div");
pdfDiv.style.display = "none";
qualifyRequest = YAHOO.util.Connect.asyncRequest('POST', "/schedule/compute_all",
@@ -403,23 +396,28 @@ function displayInfo(infoString) {
function displayErrorHTML(errorHTML) {
var errorArea = document.getElementById( "error_area" );
errorArea.innerHTML = errorHTML;
+ errorArea.style.display = "block";
}
function displayError(errorString) {
var errorArea = document.getElementById( "error_area" );
errorArea.innerHTML = "Errors:
" + errorString.replace(/ /g, " " ).replace( /\n/g, "
" );
+ errorArea.style.display = "block";
}
-function handleQualifierException(exception) {
- if(exception.name == "TooManySchedulesException") {
- var tooManyArea = document.getElementById( "too_many_explanation" );
- var tooManyNumber = document.getElementById( 'too_many_number' );
-
- tooManyNumber.innerHTML = exception.numClasses;
+function handleQualifierError(error) {
+ if(error.type == "large_search_space") {
+ var tooManyArea = document.getElementById("too_many_explanation");
+ var tooManyNumber = document.getElementById('too_many_number');
+ tooManyNumber.innerHTML = error.size;
tooManyArea.style.display = "block";
- }
- else {
- displayError( exception.string );
+ } else if(error.type == "no_query_results") {
+ displayErrorHTML("
- The Course Qualifier helps you generate all possible course timetables without time conflicts. You can choose a path that allows you to make the most efficient use of your time. + The Course Qualifier helps you generate all possible course timetables without time conflicts. You can choose a path that allows you to make the most efficient use of your time.
- Source now available on github
+ Source now available on github. Project issues on Feds SDN.
Facebook,
@@ -146,6 +146,9 @@
+ Want more control? Toggle Advanced Options! +
+
Too many results? Filter some out!
@@ -226,14 +234,12 @@
Statisfied with your schedule? Make a PDF out of it by clicking "PDF Version".
- What are others taking? View the statistics! -