Commit ed38fd6c authored by Zac Wood's avatar Zac Wood
Browse files

Now handles multiple semesters correctly

parent 3918e500
Pipeline #2987 failed with stage
in 2 minutes and 15 seconds
# Configures the application. # Configures the application.
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session protect_from_forgery with: :null_session
before_action :set_cookies, :set_cart before_action :set_cookies, :set_cart, :set_semester
def set_cart def set_cart
@cart = cookies[:ids].split(',').map do |crn| @cart = cookies[:ids].split(',').map do |crn|
...@@ -12,4 +12,12 @@ class ApplicationController < ActionController::Base ...@@ -12,4 +12,12 @@ class ApplicationController < ActionController::Base
def set_cookies def set_cookies
cookies[:ids] = "" if cookies[:ids].nil? cookies[:ids] = "" if cookies[:ids].nil?
end end
def set_semester
@semester = if params.key?(:semester_id)
Semester.find_by(id: params[:semester_id])
else
Semester.find_by(season: 'Fall', year: '2018')
end
end
end end
class SearchController < ApplicationController class SearchController < ApplicationController
def index def index
@courses = Course.where(subject: params[:q]).select do |course| @courses = Course.where(subject: params[:q], semester: @semester).select do |course|
course.course_sections.count.positive? course.course_sections.count.positive?
end end
end end
def update def update
cookies[:ids] = params[:ids] cookies[:ids] = params[:ids]
head :ok
end end
end end
...@@ -10,10 +10,4 @@ class Course < ApplicationRecord ...@@ -10,10 +10,4 @@ class Course < ApplicationRecord
validates :course_number, presence: true validates :course_number, presence: true
validates :subject, presence: true validates :subject, presence: true
validates :semester_id, presence: true validates :semester_id, presence: true
# Returns all +CourseSection+ objects that belong to this course.
# @return [Array]
def course_sections
CourseSection.where course_id: id
end
end end
# Contains logic belonging to the +CourseSection+ model. # Contains logic belonging to the +CourseSection+ model.
#
# TODO: Add more docs
class CourseSection < ApplicationRecord class CourseSection < ApplicationRecord
# Each +CourseSection+ belongs to a +Course+ and an +Instructor+. # Each +CourseSection+ belongs to a +Course+ and an +Instructor+.
belongs_to :course belongs_to :course
......
require 'icalendar' require 'icalendar'
require 'time' require 'time'
# Creates a iCal object given a list of CRNs
class Schedule class Schedule
def initialize(crns) def initialize(crns)
@cal = Icalendar::Calendar.new @cal = Icalendar::Calendar.new
......
...@@ -3,12 +3,9 @@ ...@@ -3,12 +3,9 @@
# A +Semester+ is a simple model that consists of a +year+ and a +season+, e.g. "Fall 2018". # A +Semester+ is a simple model that consists of a +year+ and a +season+, e.g. "Fall 2018".
class Semester < ApplicationRecord class Semester < ApplicationRecord
has_many :courses has_many :courses
has_many :closures
# Ensure necessary fields are present. # Ensure necessary fields are present.
validates :year, presence: true validates :year, presence: true
validates :season, presence: true validates :season, presence: true
def courses
Course.where semester_id: id
end
end end
# Registers all routes for the app. # Registers all routes for the app.
Rails.application.routes.draw do Rails.application.routes.draw do
get 'search', to: 'search#index' get 'search', to: 'search#index'
get 'search/update', to: 'search#update' get 'search/update', to: 'search#update', as: 'update_cookie'
resources :instructors, only: [:index, :show] resources :instructors, only: [:index, :show]
......
...@@ -16,9 +16,9 @@ module PatriotWeb ...@@ -16,9 +16,9 @@ module PatriotWeb
}) })
end end
def fetch_courses_in_subject(subject) def fetch_courses_in_subject(semester_val, subject)
HTTParty.post('https://patriotweb.gmu.edu/pls/prod/bwckschd.p_get_crse_unsec', HTTParty.post('https://patriotweb.gmu.edu/pls/prod/bwckschd.p_get_crse_unsec',
body: "term_in=201870&sel_subj=dummy&sel_day=dummy&sel_schd=dummy&sel_insm=dummy&sel_camp=dummy&sel_levl=dummy&sel_sess=dummy&sel_instr=dummy&sel_ptrm=dummy&sel_attr=dummy&sel_subj=#{subject}&sel_crse=&sel_title=&sel_schd=%25&sel_from_cred=&sel_to_cred=&sel_camp=%25&sel_levl=%25&sel_ptrm=%25&sel_instr=%25&begin_hh=0&begin_mi=0&begin_ap=x&end_hh=0&end_mi=0&end_ap=x", body: "term_in=#{semester_val}&sel_subj=dummy&sel_day=dummy&sel_schd=dummy&sel_insm=dummy&sel_camp=dummy&sel_levl=dummy&sel_sess=dummy&sel_instr=dummy&sel_ptrm=dummy&sel_attr=dummy&sel_subj=#{subject}&sel_crse=&sel_title=&sel_schd=%25&sel_from_cred=&sel_to_cred=&sel_camp=%25&sel_levl=%25&sel_ptrm=%25&sel_instr=%25&begin_hh=0&begin_mi=0&begin_ap=x&end_hh=0&end_mi=0&end_ap=x",
headers: { headers: {
'Content-Type' => 'application/x-www-form-urlencoded', 'Content-Type' => 'application/x-www-form-urlencoded',
'charset' => 'utf-8' 'charset' => 'utf-8'
......
...@@ -19,7 +19,6 @@ module PatriotWeb ...@@ -19,7 +19,6 @@ module PatriotWeb
def parse_semesters def parse_semesters
response = @networker.fetch_page_containing_semester_data response = @networker.fetch_page_containing_semester_data
document = Nokogiri::HTML(response) # parse the document from the HTTP response document = Nokogiri::HTML(response) # parse the document from the HTTP response
get_semesters_from_option_values(document).compact get_semesters_from_option_values(document).compact
end end
...@@ -33,8 +32,8 @@ module PatriotWeb ...@@ -33,8 +32,8 @@ module PatriotWeb
# Parses all courses belonging to a given subject # Parses all courses belonging to a given subject
# @param subject [String] # @param subject [String]
def parse_courses_in_subject(subject) def parse_courses_in_subject(semester, subject)
response = @networker.fetch_courses_in_subject(subject) response = @networker.fetch_courses_in_subject(semester, subject)
document = Nokogiri::HTML(response) document = Nokogiri::HTML(response)
get_courses(document, subject) get_courses(document, subject)
end end
...@@ -46,9 +45,10 @@ module PatriotWeb ...@@ -46,9 +45,10 @@ module PatriotWeb
# @param document [Nokogiri::HTML::Document] # @param document [Nokogiri::HTML::Document]
def get_semesters_from_option_values(document) def get_semesters_from_option_values(document)
document.css('option').map do |opt| # for each option value document.css('option').map do |opt| # for each option value
if opt.attr('value').start_with? '20' # ensure it is a semester value next unless opt.attr('value').start_with? '20' # ensure it is a semester value
opt.attr('value') # return the value text = opt.text.gsub(/ *\(.*\).*/, "").gsub(/ +/, " ")
end season, year = text.split
{ value: opt.attr('value'), season: season, year: year }
end end
end end
...@@ -57,9 +57,7 @@ module PatriotWeb ...@@ -57,9 +57,7 @@ module PatriotWeb
# @param document [Nokogiri::HTML::Document] # @param document [Nokogiri::HTML::Document]
def get_subject_codes_from_option_values(document) def get_subject_codes_from_option_values(document)
document.xpath('//*[@id="subj_id"]/option').map do |opt| # for each option value under "subj_id" document.xpath('//*[@id="subj_id"]/option').map do |opt| # for each option value under "subj_id"
if opt.attr('value').strip.alpha? # if the value is alphanumeric opt.attr('value') if opt.attr('value').strip.alpha? # return the value if the value is alphanumeric
opt.attr('value') # return the value
end
end end
end end
...@@ -69,10 +67,6 @@ module PatriotWeb ...@@ -69,10 +67,6 @@ module PatriotWeb
def get_courses(document, _subject) def get_courses(document, _subject)
table = document.css('html body div.pagebodydiv table.datadisplaytable') table = document.css('html body div.pagebodydiv table.datadisplaytable')
rows = table.css('tr') rows = table.css('tr')
# rows[100..110].each_with_index do |row, i|
# puts i
# puts row
# end
data_from rows data_from rows
end end
...@@ -95,7 +89,7 @@ module PatriotWeb ...@@ -95,7 +89,7 @@ module PatriotWeb
data[:section] = title_elements[3].strip data[:section] = title_elements[3].strip
details = rows[i + 2].css('td table tr td') details = rows[i + 2].css('td table tr td')
unless !details.empty? if details.empty?
# puts "#{full_name.join(' ')} is fake news" # puts "#{full_name.join(' ')} is fake news"
i += 1 i += 1
next next
......
...@@ -23,23 +23,27 @@ def parse_courses(subjects) ...@@ -23,23 +23,27 @@ def parse_courses(subjects)
end end
def load_courses(courses, semester) def load_courses(courses, semester)
courses.each do |course| insert_hashes = courses.map do |course|
Course.create!(subject: course[:subject], {
title: course[:title], subject: course[:subject],
course_number: course[:course_number], title: course[:title],
credits: course[:credits], course_number: course[:course_number],
description: course[:description], credits: course[:credits],
semester: semester) description: course[:description],
semester: semester
}
end end
Course.create!(insert_hashes)
end end
def parse_sections(subjects) def parse_sections(semester, subjects)
parser = PatriotWeb::Parser.new parser = PatriotWeb::Parser.new
sections_in = {} sections_in = {}
threads = subjects.map do |subject| threads = subjects.map do |subject|
Thread.new do Thread.new do
sections_in[subject] = parser.parse_courses_in_subject(subject) sections_in[subject] = parser.parse_courses_in_subject(semester, subject)
end end
end end
...@@ -91,39 +95,44 @@ def wipe_db ...@@ -91,39 +95,44 @@ def wipe_db
Semester.delete_all Semester.delete_all
end end
def load_closures(semester) def load_closures
# create closures for the days there will be no classes # create closures for the days there will be no classes
# see: https://registrar.gmu.edu/calendars/fall-2018/ # see: https://registrar.gmu.edu/calendars/fall-2018/
Closure.create! date: Date.new(2018, 9, 3), semester: semester fall2018 = Semester.find_by(season: 'Fall', year: '2018')
Closure.create! date: Date.new(2018, 10, 8), semester: semester Closure.create! date: Date.new(2018, 9, 3), semester: fall2018
(21..25).each { |n| Closure.create! date: Date.new(2018, 11, n), semester: semester } Closure.create! date: Date.new(2018, 10, 8), semester: fall2018
(10..19).each { |n| Closure.create! date: Date.new(2018, 12, n), semester: semester } (21..25).each { |n| Closure.create! date: Date.new(2018, 11, n), semester: fall2018 }
(10..19).each { |n| Closure.create! date: Date.new(2018, 12, n), semester: fall2018 }
end end
def main def main
wipe_db wipe_db
parser = PatriotWeb::Parser.new parser = PatriotWeb::Parser.new
semesters = [parser.parse_semesters.first] # expand to include however many semesters you want
courses = nil
puts "Parsing subjects..." semesters.each do |semester|
semester = parser.parse_semesters.first puts "#{semester[:season]} #{semester[:year]}"
subjects = parser.parse_subjects(semester) db_semester = Semester.create!(season: semester[:season], year: semester[:year])
puts "Parsing courses from catalog.gmu.edu..." puts "\tParsing subjects..."
courses = parse_courses(subjects) subjects = parser.parse_subjects(semester[:value])
db_semester = Semester.create! season: 'Fall', year: 2018 puts "\tParsing courses from catalog.gmu.edu..."
courses = parse_courses(subjects) if courses.nil?
puts "Loading courses..." puts "\tLoading courses..."
load_courses(courses, db_semester) load_courses(courses, db_semester)
puts "Parsing sections from Patriot Web..." puts "\tParsing sections from Patriot Web..."
sections_in = parse_sections(subjects) sections_in = parse_sections(semester[:value], subjects)
puts "Loading sections..." puts "\tLoading sections..."
load_sections(sections_in, db_semester) load_sections(sections_in, db_semester)
end
load_closures(db_semester) load_closures
end end
main main
require 'test_helper' require 'test_helper'
class SearchControllerTest < ActionDispatch::IntegrationTest class SearchControllerTest < ActionDispatch::IntegrationTest
# test "should get index" do test "should get index" do
# get search_index_url get search_url
# assert_response :success assert_response :success
# end end
test "should update cookie" do
get update_cookie_url crns: '71926,71924'
assert_response :success
end
end end
...@@ -36,5 +36,31 @@ cs211001: ...@@ -36,5 +36,31 @@ cs211001:
start_time: 2:30 pm start_time: 2:30 pm
end_time: 3:00 pm end_time: 3:00 pm
location: ENGR 200 location: ENGR 200
course: cs211 course: cs211spring
instructor: otten instructor: otten
cs112001spring:
name: CS 112 001
crn: 70192
title: Introduction to Computing
start_date: 2018-05-21
end_date: 2018-06-04
days: MWF
start_time: 12:00 pm
end_time: 1:00 pm
location: Innovation Hall 204
course: cs112spring
instructor: kinga
acct110001:
name: ACCT 110 001
crn: 71926
title: Business
start_date: 2018-05-21
end_date: 2018-06-04
days: MWF
start_time: 12:00 pm
end_time: 1:00 pm
location: Innovation Hall 204
course: acct110spring
instructor: business_man
...@@ -10,12 +10,17 @@ cs211: ...@@ -10,12 +10,17 @@ cs211:
course_number: 211 course_number: 211
semester: fall2018 semester: fall2018
acct110: acct110spring:
subject: ACCT subject: ACCT
course_number: 110 course_number: 110
semester: spring2018 semester: spring2018
cs110: cs110spring:
subject: CS subject: CS
course_number: 110 course_number: 110
semester: spring2018 semester: spring2018
cs112spring:
subject: CS
course_number: 112
semester: spring2018
...@@ -8,3 +8,6 @@ otten: ...@@ -8,3 +8,6 @@ otten:
kinga: kinga:
name: Kinga name: Kinga
business_man:
name: Bob
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment