def post(self):
        import json
        from model import Student, Lesson
        from all_exceptions import StudentLoginException
        from datetime import datetime
        from helpers import log

        try:
            self.load_search_party_context(user_type="student")

            # Get CGI form fields.
            lesson_code = self.request.get('lesson_code')
            student_nickname = self.request.get('student_nickname')
            ext = int(self.request.get('ext', 0))

            # Normalize whitespace in student name.
            # Replace any string of >=1 whitespace with a single space (equivalent to s/\s+/ /g).
            student_nickname = " ".join(student_nickname.split())

            if not lesson_code:
            # No lesson code
                raise StudentLoginException("Please enter a lesson code.",
                        "lesson_code==%r"%lesson_code)
                
            # If no student nickname, generate an anonymous one
            anonymous = False
            if not student_nickname:
                import random, string
                alphabet = string.letters + string.digits

                anonymous_student = None
                for i in range(8):
                    random_nickname = "".join(random.choice(alphabet) for i in range(10))
                    key_name = Student.make_key_name(student_nickname=random_nickname, lesson_code=lesson_code)
                    anonymous_student = Student.get_by_key_name(key_name)
                    if anonymous_student is None:
                        student_nickname = random_nickname
                        anonymous = True
                        break
    
            if anonymous and not student_nickname:
            # No student name
                raise StudentLoginException("Could not login as anonymous student.",
                        "student_nickname==%r"%student_nickname)

#            if not lesson_code and not student_nickname:
#            # Blank form
#                raise StudentLoginException("Please enter a lesson code and a student name.",
#                        "lesson_code==%r, student_nickname==%r"%(lesson_code, student_nickname))
#            elif not lesson_code:
#            # No lesson code
#                raise StudentLoginException("Please enter a lesson code.",
#                        "lesson_code==%r"%lesson_code)
#            elif not student_nickname:
#            # No student name
#                raise StudentLoginException("Please enter a student name.",
#                        "student_nickname==%r"%student_nickname)

            lesson = Lesson.get_by_key_name(lesson_code)
            # Retrieve lesson from DB
            # - If lesson does not exist, this will return None.
            # - If lesson existed but is disabled, it will return the lesson, but lesson.is_active will be False.
            # - If lesson existed but was deleted (hidden), it will return the lesson, but lesson.is_deleted will be True.
            #   (Deleting lessons is done lazily.  Actually, they are merely hidden from the teacher's view.)

            if lesson is None or lesson.is_deleted:
            # Lesson does not exist or was deleted (hidden).
                raise StudentLoginException("Please check the lesson code.",
                        "lesson retrieved from datastore with lesson_code %r is None"%lesson_code)
            elif not lesson.is_active:
            # Lesson has been disabled by teacher.  Students are not allowed to work on it anymore.
                raise StudentLoginException("This lesson is finished.  You cannot work on it now.",
                        "lesson_code %r has is_active=False"%lesson_code)
            
            # Fetch student from DB.
            # - Might return None if nobody has ever logged in with this nickname+lesson combination.
            key_name = Student.make_key_name(student_nickname=student_nickname, lesson_code=lesson_code)
            student = Student.get_by_key_name(key_name)
            login_timestamp = datetime.now()

            if student is not None:
            # Found the student.
                student.session_sid=self.session.sid
                student.latest_login_timestamp = login_timestamp
                student.latest_logout_timestamp = None
                if not student.first_login_timestamp:
                    student.first_login_timestamp = login_timestamp

            else:                
                student = Student(
                    key_name=key_name,
                    nickname=student_nickname,
                    teacher=lesson.teacher_key,
                    lesson=lesson,
                    task_idx=self.INITIAL_TASK_IDX,
                    first_login_timestamp=login_timestamp,
                    latest_login_timestamp=login_timestamp,
                    latest_logout_timestamp=None,
                    session_sid=self.session.sid,
                    anonymous=anonymous,
                    client_ids=[]
                )

            assert student.session_sid is not None
            student.put()
            self.set_person(student)
            displayName = "Anonymous" if self.is_student and self.person.anonymous else self.person.nickname
            self.session['msg'] = "Student logged in:  Hello " + displayName
            self.response.headers.add_header('Content-Type', 'application/json', charset='utf-8')
            self.response.out.write(json.dumps({"status":"logged_in", "ext":ext}))
            log( "=> LOGIN SUCCESS" )

        except StudentLoginException, e:
            e.log()
            self.set_person(None)
            msg = e.args[0]
            self.session['msg'] = msg
            self.response.headers.add_header('Content-Type', 'application/json', charset='utf-8')
            self.response.out.write(json.dumps({"status":"logged_out", "error":msg}))
            log( "=> LOGIN FAILURE:  %s"%msg )
    def _attempt_to_identify_as_student(self):
        from model import Student
        from helpers import log

        # Initialize student
        # We still store changes to student record only, if any, but we don't want to store
        # the record needlessly, since that consumes billable resources.
        student = None
        student_is_dirty = False

        # There are two ways to identify a student: nickname + lesson code sent via CGI,
        # or the session ID.  We will try them in that order.

        # Get CGI form values
        lesson_code = self.request.get("lesson_code", None)
        student_nickname = self.request.get("student_nickname", None)
        if lesson_code is not None and student_nickname is not None:
            # 1. Fetch student by nickname+lesson
            student_nickname = self.htmlunquote(student_nickname)
            key_name = Student.make_key_name(student_nickname=student_nickname, lesson_code=lesson_code)
            student = Student.get_by_key_name(key_name)
            log("=> SEARCHING BY NAME AND LESSON CODE: {0}, {1}, {2}".format(student_nickname, lesson_code, key_name))

            if student is not None and self.session.sid != student.session_sid:
                if student.is_logged_in:
                    # Prevent login if student already logged in to another session
                    # GAE limits # of channels so we don't want to allow too many student windows
                    from all_exceptions import StudentLoginException

                    raise StudentLoginException(
                        "Please choose another name.  Someone is already logged in as %s."
                        % (student_nickname.encode("ascii", "xmlcharrefreplace")),
                        "Session ID doesn't match.",
                        student.session_sid,
                        self.session.sid,
                        student.latest_login_timestamp,
                        student.latest_logout_timestamp,
                    )

        #                else:
        #                # Need to update session id if student was logged into another browser previously (and then logged out)
        #                    student.session_sid = self.session.sid
        #                    student_is_dirty = True

        else:
            # 2. Fetch student by session id
            student = Student.all().filter("session_sid =", self.session.sid).get()
            log("=> SEARCHING FOR STUDENT BY SESSION ID {0}".format(self.session.sid))

            if student is not None and not student.is_logged_in:
                # Passively log student in again.
                # At some point this student logged into this browser but logged out passively.
                from datetime import datetime

                student.latest_login_timestamp = datetime.now()
                student.latest_logout_timestamp = None
                student_is_dirty = True
                log("=> PASSIVE STUDENT LOGIN AFTER PASSIVE LOGOUT")

        if student_is_dirty:
            # Store changes to student record, if any.
            student.put()

        if student is not None and not student.is_logged_in:
            log("=> STUDENT IS **NOT** LOGGED IN")

        return student