-- -- /www/doc/sql/education.sql -- -- by randyg@mit.edu and aileen@mit.edu on September 29, 1999 -- with much help from philg@arsdigita.com on October 28, 1999 -- the system is centered around the classes -- as it is too difficult to have 2 types of queries to support -- individual users -- instead of having a classes table, we just define a user group -- type of "edu_class" -- for the class name we'll use "group_name" (a default field from -- the user_groups table); everything else will have to be in -- edu_class_info; this is a bit tricky since we need to -- keep the definitions for the helper edu_classes_info table in -- sync with what we insert into user_group_type_fields (used -- to generate UI) -- we don't store much contact info in the classes table; if we need -- to send out a report on system usage, we send it to all the people -- with the admin role in this user group -- this table holds the terms for the classes (e.g. Fall 1999) create sequence edu_term_id_sequence start with 1; create table edu_terms ( term_id integer not null primary key, term_name varchar(100) not null, start_date date not null, end_date date not null ); create or replace view edu_terms_current as select term_id, term_name, start_date, end_date from edu_terms where start_date < sysdate and end_date > sysdate; -- we want the above table to automatically start with a term that extends over all time -- (or at least 100 years) for classes that people take at their own pace insert into edu_terms (term_id, term_name, start_date, end_date) select edu_term_id_sequence.nextval, 'No Term', sysdate, add_months(sysdate,1200) from dual where 0 = (select count(*) from edu_terms); -- for a multi-department university, we need to this to sort courses -- by department; we're going to want private discussion groups, etc. -- for people who work in departments, so we make this a user group -- to find the department head and other big staffers, we look at people with -- particular roles in the user_group_map create table edu_department_info ( group_id integer primary key references user_groups, -- for schools like MIT where each department has a number department_number varchar(100), -- we'll generate a home page for them but if they have one already -- we can provide a link external_homepage_url varchar(200), mailing_address varchar(200), phone_number varchar(20), fax_number varchar(20), inquiry_email varchar(50), description clob, mission_statement clob, last_modified date default sysdate not null, last_modifying_user references users, modified_ip_address varchar(20) ); -- we want to audit the department information create table edu_department_info_audit ( group_id integer, department_number varchar(100), external_homepage_url varchar(200), mailing_address varchar(200), phone_number varchar(20), fax_number varchar(20), inquiry_email varchar(50), description clob, mission_statement clob, last_modified date, last_modifying_user integer, modified_ip_address varchar(20) ); -- we create a trigger to keep the audit table current create or replace trigger edu_department_info_audit_tr before update or delete on edu_department_info for each row begin insert into edu_department_info_audit ( group_id, department_number, external_homepage_url, mailing_address, phone_number, fax_number, inquiry_email, description, mission_statement, last_modified, last_modifying_user, modified_ip_address) values ( :old.group_id, :old.department_number, :old.external_homepage_url, :old.mailing_address, :old.phone_number, :old.fax_number, :old.inquiry_email, :old.description, :old.mission_statement, :old.last_modified, :old.last_modifying_user, :old.modified_ip_address); end; / show errors -- now, lets create a group of type department and insert all of -- the necessary rows to generate the user interface on the /admin pages declare n_departments_group_types integer; begin select count(*) into n_departments_group_types from user_group_types where group_type = 'edu_department'; if n_departments_group_types = 0 then insert into user_group_types (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy, group_module_administration) values ('edu_department','Department','Departments','wait','open','full'); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_department', 'department_number', 'Department Number', 'text', 'varchar(100)', '', 1); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_department', 'external_homepage_url', 'External Homepage URL', 'text', 'varchar(200)', '', 2); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_department', 'mailing_address', 'Mailing Address', 'text', 'varchar(200)', '', 3); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_department', 'phone_number', 'Phone Number', 'text', 'varchar(20)', '', 4); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_department', 'fax_number', 'Fax Number', 'text', 'varchar(20)', '', 5); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_department', 'inquiry_email', 'Inquiry Email', 'text', 'varchar(50)', '', 6); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_department', 'description', 'Description', 'text', 'clob', '', 7); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_department', 'mission_statement', 'Mission Statement', 'text', 'clob', '', 8); end if; end; / -- now we want to create a view to easily select departments create or replace view edu_departments as select user_groups.group_id as department_id, group_name as department_name, department_number, external_homepage_url, mailing_address, phone_number, fax_number, inquiry_email, description, mission_statement from user_groups, edu_department_info where user_groups.group_id = edu_department_info.group_id and group_type = 'edu_department' and active_p = 't' and approved_p = 't'; -- we model the subjects offered by departments create sequence edu_subject_id_sequence; -- we don't store the subject number in edu_subjects because a joint subject -- may have more than one number create table edu_subjects ( subject_id integer primary key, subject_name varchar(100) not null, description varchar(4000), description_html_p char(1) default 'f' constraint edu_sub_desc_html_p_ck check(description_html_p in ('t','f')), -- at MIT this will be a string like "3-0-9" credit_hours varchar(50), prerequisites varchar(4000), professors_in_charge varchar(200), last_modified date default sysdate not null, last_modifying_user not null references users, modified_ip_address varchar(20) not null ); -- we want to audit edu_subjects create table edu_subjects_audit ( subject_id integer, subject_name varchar(100), description varchar(4000), description_html_p char(1), credit_hours varchar(50), prerequisites varchar(4000), professors_in_charge varchar(200), last_modified date, last_modifying_user integer, modified_ip_address varchar(20) ); -- we create a trigger to keep the audit table current create or replace trigger edu_subjects_audit_trigger before update or delete on edu_subjects for each row begin insert into edu_subjects_audit ( subject_id, subject_name, description, description_html_p, credit_hours, prerequisites, professors_in_charge, last_modified, last_modifying_user, modified_ip_address) values ( :old.subject_id, :old.subject_name, :old.description, :old.description_html_p, :old.credit_hours, :old.prerequisites, :old.professors_in_charge, :old.last_modified, :old.last_modifying_user, :old.modified_ip_address); end; / show errors create table edu_subject_department_map ( department_id integer references user_groups, subject_id integer references edu_subjects, -- this would be the full '6.014' or 'CS 101' subject_number varchar(20), grad_p char(1) default 'f' check(grad_p in ('t','f')), primary key ( department_id, subject_id ) ); -- now we create classes. A class is a particular subject being -- taught in a particular term. However, we can also have special -- cases where a class is not associated with a term and we can even -- have classes that stand by themselves and aren't associated with -- subjects, e.g., an IAP knitting course (IAP = MIT's Independent -- Activities Period) -- the PL/SQL statement cannot create the table so we do it here. -- create a table to hold the extra info for each group of type -- 'edu_classes' create table edu_class_info ( group_id integer not null primary key references user_groups, term_id integer constraint edu_class_info_term_id_nn not null references edu_terms, -- we are forcing every class to belong to a subject. -- if the class is a one time offering then it should -- be placed under the subject called "One Time Offerings" subject_id integer constraint edu_class_info_subject_nn not null references edu_subjects, -- if the class doesn't start or end on the usual -- term boundary, fill these in start_date date, end_date date, description varchar(4000), -- is the description in html? description_html_p char(1) default 'f' constraint edu_class_desc_html_p_ck check(description_html_p in ('t','f')), -- at MIT, something like 'Room 4-231, TR 1-2:30' where_and_when varchar(4000), -- I still don't agree with this column. I think we should use -- the file system to hold this and just keep a pointer to the -- syllabus. That way we would have versioning which we do not -- have now (randyg@arsdigita.com, November, 1999) syllabus_id integer references fs_files, -- we keep references to the class folders so that we can link to them directly -- from various different parts of the system. assignments_folder_id references fs_files, projects_folder_id references fs_files, lecture_notes_folder_id references fs_files, handouts_folder_id references fs_files, exams_folder_id references fs_files, -- will the class web page and the documents on -- it be open to the public? public_p char(1) default 'f' check(public_p in ('t','f')), -- do students receive grades? grades_p char(1) default 'f' check(grades_p in ('t','f')), -- will the class be divided into teams? teams_p char(1) default 'f' check(teams_p in ('t','f')), exams_p char(1) default 'f' check (exams_p in ('t', 'f')), -- does the class have a final exam? final_exam_p char(1) default 'f' check (final_exam_p in ('t','f')), last_modified date default sysdate not null, last_modifying_user references users, modified_ip_address varchar(20) ); -- this table audits edu_class_info create table edu_class_info_audit ( group_id integer, term_id integer, subject_id integer, start_date date, end_date date, description varchar(4000), description_html_p char(1), where_and_when varchar(4000), syllabus_id integer, assignments_folder_id integer, projects_folder_id integer, lecture_notes_folder_id integer, handouts_folder_id integer, exams_folder_id integer, public_p char(1), grades_p char(1), teams_p char(1), exams_p char(1), final_exam_p char(1), last_modified date, last_modifying_user integer, modified_ip_address varchar(20) ); -- we create a trigger to keep the audit table current create or replace trigger edu_class_info_audit_trigger before update or delete on edu_class_info for each row begin insert into edu_class_info_audit ( group_id, term_id, subject_id, start_date, end_date, description, description_html_p, where_and_when, syllabus_id, assignments_folder_id, projects_folder_id, lecture_notes_folder_id, handouts_folder_id, exams_folder_id, public_p, grades_p, teams_p, exams_p, final_exam_p, last_modified, last_modifying_user, modified_ip_address) values ( :old.group_id, :old.term_id, :old.subject_id, :old.start_date, :old.end_date, :old.description, :old.description_html_p, :old.where_and_when, :old.syllabus_id, :old.assignments_folder_id, :old.projects_folder_id, :old.lecture_notes_folder_id, :old.handouts_folder_id, :old.exams_folder_id, :old.public_p, :old.grades_p, :old.teams_p, :old.exams_p, :old.final_exam_p, :old.last_modified, :old.last_modifying_user, :old.modified_ip_address); end; / show errors declare n_classes_group_types integer; begin select count(*) into n_classes_group_types from user_group_types where group_type = 'edu_class'; if n_classes_group_types = 0 then insert into user_group_types (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy, group_module_administration) values ('edu_class','Class','Classes','wait','open','full'); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'term_id', 'Term Class is Taught', 'text', 'integer', 'not null references edu_terms', 1); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'subject_id', 'Subject', 'text', 'integer', 'not null references edu_subjects', 2); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'start_date', 'Date to Start Displaying Class Web Page', 'date', 'date', '', 3); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'end_date', 'Date to Stop Displaying Class Web Page', 'date', 'date', '', 4); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'description', 'Class Description', 'text', 'varchar(4000)', '', 5); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'where_and_when', 'Where and When', 'text', 'varchar(4000)', '', 6); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'syllabus_id', 'Syllabus ID', 'integer', 'integer', 'references fs_files', 7); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'assignments_folder_id', 'Assignments Folder', 'integer', 'integer', 'references fs_files', 8); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'projects_folder_id', 'Projects Folder', 'integer', 'integer', 'references fs_files', 8.5); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'lecture_notes_folder_id', 'Lecture Notes Folder', 'integer', 'integer', 'references fs_files', 9); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'handouts_folder_id', 'Handouts Folder', 'integer', 'integer', 'references fs_files', 10); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'public_p', 'Will the web page be open to the public?', 'boolean', 'char(1)', 'default ''t'' check(public_p in (''t'',''f''))', 11); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'grades_p', 'Do students recieve grades?', 'boolean', 'char(1)','default ''f'' check(grades_p in (''t'',''f''))', 12); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'teams_p', 'Will the class be divided into teams?', 'boolean', 'char(1)','default ''f'' check(teams_p in (''t'',''f''))', 13); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'exams_p', 'Will the class have exams?', 'boolean', 'char(1)','default ''f'' check(exams_p in (''t'',''f''))', 14); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'final_exam_p', 'Will the class have a final exam?', 'boolean', 'char(1)','default ''f'' check(final_exam_p in (''t'',''f''))', 15); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'exams_folder_id', 'Exams Folder', 'integer', 'integer', 'references fs_files', 16); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_class', 'description_html_p', 'Description HTML?', 'boolean', 'char(1)', 'default ''f'' check(description_html_p in (''t'',''f''))', 17); end if; end; / -- create a view for current classes whose webpages we should display -- to students create or replace view edu_current_classes as select user_groups.group_id as class_id, group_name as class_name, edu_class_info.term_id, subject_id, edu_class_info.start_date, edu_class_info.end_date, description, description_html_p, where_and_when, syllabus_id, lecture_notes_folder_id, handouts_folder_id, assignments_folder_id, projects_folder_id, exams_folder_id, public_p, grades_p, teams_p, exams_p, final_exam_p from user_groups, edu_class_info where user_groups.group_id = edu_class_info.group_id and group_type = 'edu_class' and active_p = 't' and existence_public_p='t' and approved_p = 't' and sysdate=edu_class_info.start_date; -- create a view for all active classes in the system - these are so -- professors can access the admin pages even though students don't see -- these classes create or replace view edu_classes as select user_groups.group_id as class_id, group_name as class_name, edu_class_info.term_id, subject_id, edu_class_info.start_date, edu_class_info.end_date, description, description_html_p, where_and_when, syllabus_id, lecture_notes_folder_id, handouts_folder_id, assignments_folder_id, projects_folder_id, exams_folder_id, public_p, grades_p, teams_p, exams_p, final_exam_p from user_groups, edu_class_info where user_groups.group_id = edu_class_info.group_id and group_type = 'edu_class' and active_p = 't' and existence_public_p='t' and approved_p = 't'; -- we want to be able to divide classes further into sections. -- this is nice for tutorials and recitations. -- you can get the class for the section from the parent_group_id from user_groups create table edu_section_info ( group_id integer not null references user_groups, section_time varchar(100), section_place varchar(100) ); declare n_section_group_types integer; begin select count(*) into n_section_group_types from user_group_types where group_type = 'edu_section'; if n_section_group_types = 0 then insert into user_group_types (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy,group_module_administration) values ('edu_section','Section','Sections','wait','open','full'); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_section', 'section_time', 'Section Time', 'text', 'varchar(100)', '', 2); insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) values ('edu_section', 'section_place', 'Section Place', 'text', 'varchar(100)', '', 3); end if; end; / create or replace view edu_sections as select user_groups.group_id as section_id, group_name as section_name, parent_group_id as class_id, section_time, section_place from user_groups, edu_section_info where user_groups.group_id = edu_section_info.group_id and group_type = 'edu_section' and active_p = 't' and approved_p = 't'; declare n_classes_group_types integer; begin select count(*) into n_classes_group_types from user_group_types where group_type = 'edu_department'; if n_classes_group_types = 0 then insert into user_group_types (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy, group_module_administration) values ('edu_department','Department','Departments','wait','open','none'); end if; end; / -- we are implementing teams as subgroups so lets create a view to see them create or replace view edu_teams as select group_id as team_id, group_name as team_name, parent_group_id as class_id, admin_email, registration_date, creation_user, creation_ip_address, existence_public_p, new_member_policy, email_alert_p, multi_role_p, group_admin_permissions_p, index_page_enabled_p, body, html_p, modification_date, modifying_user from user_groups where group_type = 'edu_team' and active_p = 't' and approved_p = 't'; -- Create edu_team group type declare n_teams_group_types integer; begin select count(*) into n_teams_group_types from user_group_types where group_type = 'edu_team'; if n_teams_group_types = 0 then insert into user_group_types (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy,group_module_administration) values ('edu_team','Team','Teams','wait','open','none'); end if; end; / create sequence edu_textbooks_sequence start with 1; create table edu_textbooks ( textbook_id integer not null primary key, title varchar(200), author varchar(400), publisher varchar(200), -- isbn has to be a varchar and not a number because some ISBNs have the letter -- x at the end; ISBN will be just the digits and letters mushed together -- (no dashes in between), amazon.com style isbn varchar(50) ); -- map the textbooks to classes create table edu_classes_to_textbooks_map ( textbook_id integer references edu_textbooks, class_id integer references user_groups, required_p char(1) default 't' check (required_p in ('t','f')), comments varchar(4000), primary key (class_id, textbook_id) ); create sequence edu_grade_sequence; -- records the grade types and their relative weights. this table will not -- capture the qualitative factors, but should take care of the -- quantitative portion of the final grade create table edu_grades ( grade_id integer not null primary key, grade_name varchar(100), class_id integer not null references user_groups, comments varchar(4000), -- weight is a percentage weight number check (weight between 0 and 100), last_modified date default sysdate not null, last_modifying_user not null references users, modified_ip_address varchar(20) not null ); -- we want to audit edu_grades create table edu_grades_audit ( grade_id integer, grade_name varchar(100), class_id integer, comments varchar(4000), -- weight is a percentage weight number, last_modified date, last_modifying_user integer, modified_ip_address varchar(20), delete_p char(1) default('f') check (delete_p in ('t','f')) ); -- we create a trigger to keep the audit table current create or replace trigger edu_grades_audit_trigger before update or delete on edu_grades for each row begin insert into edu_grades_audit ( grade_id, grade_name, class_id, comments, weight, last_modified, last_modifying_user, modified_ip_address) values ( :old.grade_id, :old.grade_name, :old.class_id, :old.comments, :old.weight, :old.last_modified, :old.last_modifying_user, :old.modified_ip_address); end; / show errors -- we want to be able to easily keep track of lecture notes/handouts -- note that we do not keep track of author or date uploaded or even -- a comment about it. We do not because is all kept in the -- fs_files table, which edu_handouts references. We keep the handout_name -- in both places because we will be displaying that a lot and we do not -- want to always have to join with fs_files create sequence edu_handout_id_sequence start with 1; create table edu_handouts ( handout_id integer not null primary key, class_id integer references user_groups, handout_name varchar(500) not null, file_id integer references fs_files not null, -- what kind of handout is this? Possibilities include -- lecture_notes and announcement handout_type varchar(200), -- what date was this handout given out distribution_date date default sysdate ); -- we want to be able to keep track of assignemnts within the class. create sequence edu_task_sequence; -- includes assignments, projects, exams, any tasks a student might be -- graded on create table edu_student_tasks ( task_id integer primary key, class_id not null references user_groups, grade_id references edu_grades, -- we have to have a task type so we can categorize tasks in the -- user pages task_type varchar(100) check (task_type in ('assignment', 'exam', 'project')), task_name varchar(100), description varchar(4000), -- the date we assigned/created the task date_assigned date, -- we want to know the last time the task was modified -- (the permissions were changed or a new version was uploaded, etc) last_modified date, -- could be date assignment is due, or date of an exam due_date date, -- this references the fs_files that holds either the -- actual assignment available for download or the url of the -- assignment file_id references fs_files, -- who assigned this? assigned_by not null references users, -- This column is for projects where students can -- assign themselves to teams. self_assignable_p char(1) default 'f' check (self_assignable_p in ('t','f')), self_assign_deadline date, -- how much is this assignment worth compared to the others with -- the same grade_id (e.g. under the same grade group)? -- weight is a percentage weight number check (weight between 0 and 100), requires_grade_p char(1) check (requires_grade_p in ('t','f')), -- whether the task is submitted/administered online online_p char(1) check (online_p in ('t','f')), -- if an assignment has been deleted we mark it as inactive active_p char(1) default 't' check (active_p in ('t','f')) ); -- views for assignments, exams, and projects create or replace view edu_projects as select task_id as project_id, class_id, task_type, assigned_by as teacher_id, grade_id, task_name as project_name, description, date_assigned, last_modified, due_date, file_id, self_assignable_p, self_assign_deadline, weight, requires_grade_p, online_p as electronic_submission_p from edu_student_tasks where task_type='project' and active_p='t'; create or replace view edu_exams as select task_id as exam_id, task_type, class_id, assigned_by as teacher_id, grade_id, task_name as exam_name, description as comments, date_assigned as creation_date, last_modified, due_date as date_administered, file_id, weight, requires_grade_p, online_p from edu_student_tasks where task_type='exam' and active_p='t'; create or replace view edu_assignments as select task_id as assignment_id, task_type, class_id, assigned_by as teacher_id, grade_id, task_name as assignment_name, description, date_assigned, last_modified, due_date, file_id, weight, requires_grade_p, online_p as electronic_submission_p from edu_student_tasks where task_type = 'assignment' and active_p='t'; -- we want to be able to post the solutions and associate the solutions -- to a given file create table edu_task_solutions ( task_id references edu_student_tasks, file_id references fs_files, primary key(task_id, file_id) ); -- we want a table to map student solutions to assignments -- this is what allows students to upload their finished papers, etc. create table edu_student_answers ( -- either student_id or team_id must be non-empty student_id references users, team_id references user_groups, task_id references edu_student_tasks, file_id references fs_files, -- this is the date of the last time the solutions were changed last_modified date default sysdate not null, last_modifying_user not null references users, -- modified_ip_address is stored as a string separated by periods. modified_ip_address varchar(20) not null ); create table edu_student_answers_audit ( student_id integer, task_id integer, file_id integer, -- this is the date of the last time the solutions were changed last_modified date, last_modifying_user integer, -- modified_ip_address is stored as a string separated by periods. modified_ip_address varchar(20) ); -- we create a trigger to keep the audit table current create or replace trigger edu_student_answers_audit_tr before update or delete on edu_student_answers for each row begin insert into edu_student_answers_audit ( student_id, task_id, file_id, last_modified, last_modifying_user, modified_ip_address) values ( :old.student_id, :old.task_id, :old.file_id, :old.last_modified, :old.last_modifying_user, :old.modified_ip_address); end; / show errors -- this is where we keep the student grades and the evaluations -- that students receive from teachers create sequence edu_evaluation_id_sequence; create table edu_student_evaluations ( evaluation_id integer primary key, class_id not null references user_groups, -- must have student_id or team_id student_id references users, team_id references user_groups, task_id references edu_student_tasks, -- there may be several times during the term that the prof -- wants to evaluate a student. So, the evaluation_type -- is something like 'end_of_term' or 'midterm' evaluation_type varchar(100), grader_id not null references users, grade varchar(5), comments varchar(4000), show_student_p char(1) default 't' check (show_student_p in ('t','f')), evaluation_date date default sysdate, last_modified date default sysdate not null, last_modifying_user not null references users, -- modified_ip_address is stored as a string separated by periods. modified_ip_address varchar(20) not null ); -- we want to audit the evaluations table create table edu_student_evaluations_audit ( evaluation_id integer, class_id integer, -- must have student_id or team_id student_id integer, team_id integer, task_id integer, evaluation_type varchar(100), grader_id integer, grade varchar(5), comments varchar(4000), show_student_p char(1), evaluation_date date, last_modified date, last_modifying_user integer, modified_ip_address varchar(20) ); -- we create a trigger to keep the audit table current create or replace trigger edu_student_answers_audit_tr before update or delete on edu_student_answers for each row begin insert into edu_student_answers_audit ( student_id, task_id, file_id, last_modified, last_modifying_user, modified_ip_address) values ( :old.student_id, :old.task_id, :old.file_id, :old.last_modified, :old.last_modifying_user, :old.modified_ip_address); end; / show errors -- now, we want to hold information about each task. It is possible -- to have one term task but many instances of that task. For -- instance, "Final Project for 6.916" is a term task that would -- be kept in the edu_tasks table but ArfDigita.org is a task -- instance that would be kept in this table. There is a many to -- one mapping -- we make task_id not null because every task has to be part of -- some sort of task (either an assignment or a project) -- we make it a task because all evaluations are done on tasks create sequence edu_task_instance_id_seq start with 1; create table edu_task_instances ( task_instance_id integer not null primary key, task_instance_name varchar(200), task_instance_url varchar(500), -- which task is this an instance of? task_id integer not null references edu_student_tasks, description varchar(4000), approved_p char(1) default 'f' check(approved_p in ('t','f')), approved_date date, approving_user references users(user_id), -- we want to be able to generate a consistent user interface so -- we record the type of task. -- (aileen 4/00) renamed this from task_type because task_type is -- a reserved column name in edu_student_tasks team_or_user varchar(10) default 'team' check(team_or_user in ('user','team')), min_body_count integer, max_body_count integer, -- we want to be able to "delete" task instances so we have active_p active_p char(1) default 't' check(active_p in ('t','f')) ); -- we want to be able to assign students and teams to tasks -- we use an index instead of a multi-column primary key because -- team_id and student_id an both be null create table edu_task_user_map ( task_instance_id integer not null references edu_task_instances, team_id integer references user_groups, student_id integer references users, constraint edu_task_user_map_check check ((team_id is null and student_id is not null) or (team_id is not null and student_id is null)) ); create index edu_task_map_idx on edu_task_user_map(task_instance_id, team_id, student_id); -- now create a view for project instances -- note the renaming of project_type to team_or_user as task_type is a -- reserved column name that references edu_student_tasks -- rename to project_type for now to keep code from breaking but we should -- eventually make the code reference team_or_user instead create or replace view edu_project_instances as select i.task_instance_id as project_instance_id, i.task_instance_name as project_instance_name, i.task_instance_url as project_instance_url, i.task_id as project_id, i.description, i.approved_p, i.approved_date, i.approving_user, team_or_user as project_type, min_body_count, max_body_count, i.active_p from edu_task_instances i, edu_student_tasks t where i.task_id=t.task_id and t.task_type='project'; create or replace view edu_project_user_map as select m.task_instance_id as project_instance_id, m.team_id, m.student_id from edu_task_user_map m, edu_student_tasks t, edu_task_instances i where t.task_type='project' and i.task_id=t.task_id and m.task_instance_id=i.task_instance_id; -- we want to allow classes to rename their roles. That is, -- some people want to be called Professor where others want -- to be called Instructor and still others want to be called -- Lecturer. We don't want to just use the 'role' column -- in user_group_roles because then we would not have a way -- to "spam all professors and TAs" because we would not know -- which role was a prof and which was a TA. Also, we want to -- have a sort_key so that we know which order to display these -- items when they are shown to the user. So, we have the following -- table -- so, for the case where a class wants to call the prof a Lecturer, -- we would have role = Professor and pretty_role = Lecturer create table edu_role_pretty_role_map ( group_id not null references user_groups, -- role should reference user_group_roles(role) role varchar(200), -- what the class wants to call the role pretty_role varchar(200), pretty_role_plural varchar(300), -- sort key for display of columns. sort_key integer not null, -- this is to capture info about the hierarchy of role permissions priority integer, primary key (group_id, role) ); -- we want to allow professors to have office hours and -- for students to be able to automatically sign themselves -- up as either an individual or as a team. These items -- are only used when students can self register. If they cannot -- then it is just as easy for a prof to put the event into a class -- event or under a message of the day. This could -- also be used for advisors to allow advisees to sign up for -- an appointment on registration day create sequence edu_appointments_appt_id_seq start with 1; create table edu_appointments ( appointment_id integer constraint edu_appts_appt_id_nn not null constraint edu_appts_appt_id_pk primary key, -- who is the meeting with? If this is office hours, -- for instance, the user_id would be the TA or professor user_id constraint edu_appts_user_id_nn not null constraint edu_appts_user_id_fk references users, -- if the office hours are restricted to a particular group -- then we need a group_id (e.g. if the professor teaches -- multiple classes he would only want one class to have -- office hours at a given time group_id constraint edu_appts_group_id_fk references user_groups, -- where is the meeting (office hours) place varchar(4000), -- Date and time. start_time date constraint edu_appts_start_time_nn not null, end_time date constraint edu_appts_end_time_nn not null, -- The number of minutes between appointments -- if 'increment' is left null or is longer than -- end_time - start_time then there is only one time slot -- to fill time_increment integer, -- should students be able to see who has signed up in the other -- slots (or should they just see something like "Not Available") public_schedule_p char(1) constraint edu_appt_public_ck check (public_schedule_p in ('t','f')), check (start_time < end_time) ); -- now, we want a way to keep track of who is signed up -- for which appointment at which time -- if the prof wants a lunch break, they just schedule themselves -- for that time block create table edu_appointments_scheduled ( appointment_id integer constraint edu_appt_sched_appt_id_nn not null constraint edu_appt_sched_appt_id_fk references edu_appointments, user_id integer constraint edu_appt_sched_user_id_fk references users, group_id integer constraint edu_appt_sched_group_id_fk references user_groups, -- Date and time. start_time date constraint edu_appt_sched_start_nn not null, end_time date constraint edu_appt_sched_end_nn not null, constraint edu_appt_sched_nn_ck check ((user_id is not null and group_id is null) or (user_id is null and group_id is not null)) ); create index edu_appt_schedule_group_idx on edu_appointments_scheduled(appointment_id, group_id); create index edu_appt_schedule_user_idx on edu_appointments_scheduled(appointment_id, user_id); -- now, we want to be able to store information about each individual in -- a class so we create an entry in user_group_type_member_fields insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'student', 'Institution ID', 'short_text', 1); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'dropped', 'Institution ID', 'short_text', 2); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'student', 'Student Account', 'short_text', 3); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'dropped', 'Student Account', 'short_text', 4); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'ta', 'Office', 'short_text', 5); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'professor', 'Office', 'short_text', 6); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'professor', 'Phone Number', 'short_text', 7); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'ta', 'Phone Number', 'short_text', 8); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'ta', 'Office Hours', 'short_text', 9); insert into user_group_type_member_fields (group_type, role, field_name, field_type, sort_key) values ('edu_class', 'professor', 'Office Hours', 'short_text', 10); ------------------------------------------------- ------------------------------------------------- -- -- BEGIN PL/SQL -- -- ------------------------------------------------- ------------------------------------------------- CREATE OR REPLACE TRIGGER edu_class_role_update_tr AFTER UPDATE OF role ON user_group_roles FOR EACH ROW BEGIN -- we want to update the existing row update edu_role_pretty_role_map set role = :new.role where group_id = :new.group_id and role = :old.role; END; / show errors -- for every row that is inserted into the user_group_roles, if -- the group is of type edu_class then we want to insert a corresponding -- role into edu_role_pretty_role_map CREATE OR REPLACE TRIGGER edu_class_role_insert_tr AFTER INSERT ON user_group_roles FOR EACH ROW DECLARE v_class_p integer; BEGIN select count(group_id) into v_class_p from user_groups where group_type = 'edu_class' and group_id = :new.group_id; IF v_class_p > 0 THEN insert into edu_role_pretty_role_map ( group_id, role, pretty_role, pretty_role_plural, sort_key, priority) select :new.group_id, :new.role, :new.role, :new.role || 's', nvl(max(sort_key),0) + 1, nvl(max(priority),0) + 1 from edu_role_pretty_role_map where group_id = :new.group_id; END IF; END; / show errors -- if a role is deleted from user_group_roles and the group -- is of type edu_class then we also want to delete it from -- edu_role_pretty_role_map CREATE OR REPLACE TRIGGER edu_class_role_delete_tr BEFORE DELETE ON user_group_roles FOR EACH ROW BEGIN delete from edu_role_pretty_role_map where group_id = :old.group_id and lower(role) = lower(:old.role); END; / show errors --------------------------------------------------- -- -- -- -- begin the portal tables -- -- -- --------------------------------------------------- -- the portal mini-tables declare n_zip_codes_tables integer; begin select count(*) into n_zip_codes_tables from user_tables where lower(table_name) = 'zip_codes'; if n_zip_codes_tables > 0 then execute immediate 'create sequence weather_id_sequence'; execute immediate 'create table portal_weather ( weather_id integer not null primary key, user_id not null references users, city varchar(100), usps_abbrev references states, zip_code varchar(10) references zip_codes, -- the type can be: next day forecast, -- 5 day forecast, current conditions five_day_p char(1) default ''f'' check (five_day_p in (''t'',''f'')), next_day_p char(1) default ''f'' check (next_day_p in (''t'',''f'')), current_p char(1) default ''f'' check (current_p in (''t'',''f'')) )'; end if; end; / create table portal_stocks ( user_id not null references users, symbol varchar(10) not null, default_p char(1) default 'f' check(default_p in ('t','f')) ); -- we're currently using the calendar module and not edu_calendar -- because the features have not been fully implemented -- this is taken from the intranet calendar create table edu_calendar_categories ( category varchar(100) primary key, enabled_p char(1) default 't' check(enabled_p in ('t','f')) ); create sequence edu_calendar_id_sequence; -- updates from intranet/doc/sql/calendar.sql: -- the addition of a viewable column that specifies whether the calendar -- entry is viewable by the public and if so, whether we should show the -- title or something in place of the title (e.g. Busy, Free, Tentative -- -- MS Outlook options). also, addition of owner column that identifies who -- the entry is for: so we can display calendars with respect to individual -- users or groups of users (like in a team) create table edu_calendar ( calendar_id integer primary key, category not null references calendar_categories, -- the way we connect calendar entries to users owner not null references users, title varchar(100) not null, body varchar(4000) not null, -- is the body in HTML or plain text (the default) html_p char(1) default 'f' check(html_p in ('t','f')), start_date date not null, -- first day of the event end_date date not null, -- last day of the event (same as start_date for single-day events) expiration_date date not null, -- day to stop including the event in calendars, typically end_date -- viewable as public means the title will be displayed. private -- means the entry will be invisible unless viewed by the -- owner. busy, free, or tentative will be displayed instead of title -- to viewers other than owner viewable varchar(100) default 'public' check(viewable in ('public', 'busy', 'free', 'tentative', 'private')), event_url varchar(200), -- URL to the event event_email varchar(100), -- email address for the event -- for events that have a geographical location country_code references country_codes(iso), -- within the US usps_abbrev references states, -- we only want five digits zip_code varchar(10), approved_p char(1) default 'f' check(approved_p in ('t','f')), creation_date date not null, creation_user not null references users(user_id), creation_ip_address varchar(50) not null ); -- start an class category so classes can have events (e.g. exam review) of -- "group" scope create or replace trigger calendar_cat_upon_new_class after insert on user_groups for each row begin IF :new.group_type='edu_class' THEN insert into calendar_categories (category_id, scope, group_id, category, enabled_p) values (calendar_category_id_sequence.nextval, 'group', :new.group_id, 'Class', 't'); END IF; end; / show errors -- create default tables for each portal -- start a personal category so the user can enter personal events of -- "user" scope create or replace trigger portal_page_upon_new_user after insert on users for each row begin insert into portal_pages (page_id, user_id, page_number) values (portal_page_id_sequence.nextval, :new.user_id, 1); insert into calendar_categories (category_id, scope, user_id, category, enabled_p) values (calendar_category_id_sequence.nextval, 'user', :new.user_id, 'Personal', 't'); end; / show errors -- the opposite of the above trigger -- for deleting users create or replace trigger portal_remove_upon_user_delete before delete on users for each row begin delete from portal_pages where user_id=:old.user_id; end; / show errors create or replace trigger portal_setup_upon_page_insert after insert on portal_pages for each row declare stock_table_id portal_tables.table_id%TYPE; weather_table_id portal_tables.table_id%TYPE; classes_table_id portal_tables.table_id%TYPE; announcements_table_id portal_tables.table_id%TYPE; calendar_table_id portal_tables.table_id%TYPE; begin select table_id into stock_table_id from portal_tables where table_name='Stock Quotes'; select table_id into weather_table_id from portal_tables where table_name='Current Weather'; select table_id into classes_table_id from portal_tables where table_name='Classes'; select table_id into announcements_table_id from portal_tables where table_name='Announcements'; select table_id into calendar_table_id from portal_tables where table_name='Calendar'; insert into portal_table_page_map (page_id, table_id, sort_key, page_side) values (:new.page_id, stock_table_id, 1, 'l'); insert into portal_table_page_map (page_id, table_id, sort_key, page_side) values (:new.page_id, weather_table_id, 2, 'l'); insert into portal_table_page_map (page_id, table_id, sort_key, page_side) values (:new.page_id, classes_table_id, 1, 'r'); insert into portal_table_page_map (page_id, table_id, sort_key, page_side) values (:new.page_id, announcements_table_id, 3, 'l'); insert into portal_table_page_map (page_id, table_id, sort_key, page_side) values (:new.page_id, calendar_table_id, 2, 'r'); end; / show errors -- the opposite of the trigger above -- upon deleting a page for portal -- table we also want to delete the entries from portal_table_page_map create or replace trigger portal_update_upon_page_delete before delete on portal_pages for each row begin delete from portal_table_page_map where page_id=:old.page_id; end; / show errors