Fix jQuery error that was causing the dashboard to get loaded twice.  (The error reporter re-downloads the current page in case of error.)
In class_manage_row.html, use get_sections() rather than .sections.all() for caching purposes
Use ClassSubject.objects.catalog() for course data in the catalog, so all relevant fields are pre-fetched
Use get_meeting_times() rather than .meeting_times to save a query
Make Program.getShirtInfo() do one query, rather than O(#teachers) queries

We now have a nice boolean field on RegistrationProfile that lets us filter by the latest profile.  Django also has shiny aggregation functions.  So we don't have to iterate through the list of users, get the profile for each user, get the teacher info off of that profile, get the T-shirt size and type off of that teacher profile, and increment a tally for that size and type.  Instead, we can just get Django to give us a table of the form

Shirt Type | Shirt Size | Count

and then perform some gymnastics to turn this into a grid with size on one axis and type on the other.

TODO:  These gymnastics are uglier than they need to be...  If we wanted to be really fancy, we could have Django return sorted output and just have the template iterate over it or something.
More of my half-formed tests.
Switch yet more instances of sections.all() for get_sections()
~20x performance improvement in rendering the cached catalog.

There's a 'for' loop in catalog.html that iterates over all of the classes, printing a short section header if at the start of a new section and then calling out to a Python function to render each actual class entry.  The Python function is fully cached.

Rather than have the 'for' loop in the template, this code instead calls the render function directly for each class, and inserts the resulting strings into an array.  Section headers are also inserted into the array at the proper locations.

Then I call ''.join(array) and ship the whole giant string as a variable to be inserted into the catalog.

Turns out ''.join is a whole lot more efficient than Django Template 'for' loops for large output.

This code also starts to add some arguments to the render functions that let people pass in various useful parameters rather than having the render function do a DB query per class to look them up.
Made progress on testing of QSD caching

I have a test that theoretically should work. Currently it's
failing because Django's caching is getting in the way. I posted
a comment to Github, waiting for a response.

I also moved some of the common selenium testing functions out of
the specific CSRF selenium test class and into the general module.
The CSRF test now just includes and uses the functions (and still
works). My new test for QSD caching uses these as well.
Remove the csrf token from the default login box.  It's added by JavaScript nowadays anyway; and with HTTP-level caching it's likely to be incorrect.
Force-disable setting a new CSRF cookie.

Some views, for now QSD and the catalog, are cached at the HTTP level.  That means that it's not safe for those pages to set a new CSRF cookie for users who visit them:  This will either defeat the caching or cause everyone who visits the page to be given a copy of the CSRF token of the first user who went there.

So, add a decorator for a view that will cause the csrf middleware to not set a csrf cookie for any response from that view.  And add the decorator to the catalog and to all QSD pages.

Note that this breaks non-public QSD pages:  Once one person has seen the page, anyone who knows the URL can go and see the page.  Does anyone use QSD pages that are not public, though?  I feel like we have no current uses of it, and periodic issues from people who assume that all pages are always publicly visible but who can't see them because the relevant UserBit isn't set.
Fix list index error in customforms.views.hasPerm

The code assumed that form.perms always had at least three
comma-separated values, even though it is possible to set permissions
with only one value. You can now safely view a form that has
permissions set, without those permissions being for a specific
program.
In ProgramFrameworkTest, give each program its own QSD homepage
Test that the 'Vary: Cookie' header is not being set for QSD and Catalog pages.
Merge branch 'main' of github.com:learning-unlimited/ESP-Website into ajaxroomfilter

Conflicts:
	esp/public/media/scripts/ajaxschedulingmodule/ESP/Matrix.js
	esp/public/media/scripts/ajaxschedulingmodule/ESP/Scheduling.js
Scheduling: don't short-schedule classes. (#117)

Apparently ClassSection.assign_room left its partial results in place if
it failed to schedule some rooms. This old behavior can be requested
with the new "allow_partial" keyword argument to assign_room; but now
the default is to undo partial results if the assignment fails.

This particular method of undoing already-assigned rooms assumes that
the until the first failure, we managed to get exclusive access to each
(room, time) for this ClassSection. In the future we may wish for
something more atomic.
Merge branch 'main' of github.com:learning-unlimited/ESP-Website into main
Scheduling: ClassSection.assign_room test.

This includes a regression test for the previous commit. If you try to
schedule a class (2) into conflict with an already-scheduled class (1),
then (1)'s schedule should be unaffected and (2)'s should be empty.

I put this in as an AJAXSchedulingModule test, despite the fact that it
only actually tests the API we've built into our models.
Clarify ClassSection.assign_room docstring.

The first time I read that, I thought it meant "assign the classroom
given, at all times except those needed by this class". I suspect
removing "except" makes it less likely to be misinterpreted.
Scheduling: Show correct class in conflict msg.

Say which class occupies the room, specifically at the time requested.
This should fix the messages about conflicts with classes that aren't
even from the same program.

The old behavior would say the room was occupied by whichever class was
the first to occupy a room with that name (or none at all, if the first
(room, time) pair with that name is empty). That might've been the
reason for the paranoid comment on the next line after.