public void CrossListedCourses_CommonCourse() { ApiController target = new ApiController(); string courseID = ConstructCourseID(CourseID.FromString("ART","107"), true); JsonResult actual = target.CrossListedCourses(courseID, "** INVALID YRQ **"); Assert.IsNotNull(actual, "Returned Result is NULL"); Assert.IsNotNull(actual.Data, "JSON data is NULL"); Assert.IsInstanceOfType(actual.Data, typeof(IEnumerable<CrossListedCourseModel>)); IEnumerable<CrossListedCourseModel> obj = actual.Data as IEnumerable<CrossListedCourseModel>; Assert.IsNotNull(obj); string[] sectionIDs = obj.Select(o => o.SectionID.ToString()).ToArray(); if (sectionIDs.Length > 0) { using (ClassScheduleDb db = new ClassScheduleDb()) { IQueryable<SectionCourseCrosslisting> crosslistings = from x in db.SectionCourseCrosslistings where sectionIDs.Contains(x.ClassID) select x; Assert.IsTrue(crosslistings.Any(), "Did not find matching cross-listing record in the Class Schedule database. ('{0}' => [{1}]", courseID, sectionIDs.Mash()); } } else { Assert.Inconclusive("The method did not return any cross-linked SectionIDs for '{0}'", courseID); } }
/// <summary> /// Finds a <see cref="Subject"/> based on its <paramref name="slug"/> identifier /// </summary> /// <param name="slug">The slug identifier for a given <see cref="Subject"/></param> /// <returns><see cref="Subject"/> if found, otherwise <see langword="null" /></returns> public static Subject GetSubject(string slug) { using (ClassScheduleDb context = new ClassScheduleDb()) { Subject subject = context.Subjects.Where(s => s.Slug.Equals(slug, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); return subject; } }
public ActionResult AllClasses(string letter, string format) { using (OdsRepository repository = new OdsRepository(HttpContext)) { // TODO: Refactor the following code into its own method // after reconciling the noted differences between AllClasses() and YearQuarter() - 4/27/2012, [email protected] using (ClassScheduleDb db = new ClassScheduleDb()) { // force CoursePrefixes to load up front for Subjects (otherwise we get an error trying to load after db has been disposed) db.ContextOptions.LazyLoadingEnabled = false; IList<Subject> subjects = db.Subjects.Include("CoursePrefixes").ToList(); IList<char> subjectLetters = subjects.Select(s => s.Title.First()).Distinct().ToList(); if (letter != null) { subjects = subjects.Where(s => s.Title.StartsWith(letter, StringComparison.OrdinalIgnoreCase)).ToList(); } #if DEBUG Debug.Print("======= Subject list ======="); foreach (Subject subject in subjects) { Debug.Print("{0} ({1})", subject.Title, subject.CoursePrefixes.Select(p => p.CoursePrefixID).ToArray().Mash(", ")); } Debug.Print("===== END Subject list ====="); #endif // Construct the model AllClassesModel model = new AllClassesModel { CurrentQuarter = repository.CurrentYearQuarter, NavigationQuarters = Helpers.GetYearQuarterListForMenus(repository), Subjects = subjects.Select(s => new SubjectModel { Title = s.Title, Slug = s.Slug, CoursePrefixes = s.CoursePrefixes.Select(p => p.CoursePrefixID).ToList() }).ToList(), LettersList = subjectLetters, ViewingLetter = String.IsNullOrEmpty(letter) ? (char?)null : letter.First() }; if (format == "json") { // NOTE: AllowGet exposes the potential for JSON Hijacking (see http://haacked.com/archive/2009/06/25/json-hijacking.aspx) // but is not an issue here because we are receiving and returning public (e.g. non-sensitive) data return Json(model, JsonRequestBehavior.AllowGet); } // set up all the ancillary data we'll need to display the View SetCommonViewBagVars(repository, string.Empty, letter); ViewBag.LinkParams = Helpers.getLinkParams(Request); return View(model); } } }
/// <summary> /// Takes a course <paramref name="prefix"/> parameter and returns all <see cref="Subject"/>s which own the prefix <see cref="Subject"/> entity. /// </summary> /// <param name="prefix"></param> /// <returns>The a list of the matching <see cref="Subject"/> entities</returns> public static Subject GetSubjectFromPrefix(string prefix) { Subject result = null; using (ClassScheduleDb db = new ClassScheduleDb()) { // The CoursePrefixID is the primary key, so we know there will not be more than one result result = db.SubjectsCoursePrefixes.Where(s => s.CoursePrefixID.Equals(prefix, StringComparison.OrdinalIgnoreCase)) .Select(s => s.Subject).FirstOrDefault(); } if (result == null) { _log.Warn(m => m("Failed to retrieve Subject record for PrefixID '{0}'", prefix)); return result; } return result; }
/// <summary> /// /// </summary> /// <param name="yearQuarter"></param> /// <returns></returns> public static IList<ScheduleCoursePrefix> GetSubjectList(string yearQuarter) { IList<ScheduleCoursePrefix> subjectList; using (OdsRepository db = new OdsRepository()) { IList<CoursePrefix> data; data = string.IsNullOrWhiteSpace(yearQuarter) || yearQuarter.Equals("All", StringComparison.OrdinalIgnoreCase) ? db.GetCourseSubjects() : db.GetCourseSubjects(YearQuarter.FromFriendlyName(yearQuarter)); IList<string> apiSubjects = data.Select(s => s.Subject).ToList(); using (ClassScheduleDb classScheduleDb = new ClassScheduleDb()) { // Entity Framework doesn't support all the functionality that LINQ does, so first grab the records from the database... var dbSubjects = (from s in classScheduleDb.Subjects join p in classScheduleDb.SubjectsCoursePrefixes on s.SubjectID equals p.SubjectID select new { s.Slug, p.CoursePrefixID, s.Title }) .ToList(); // ...then apply the necessary filtering (e.g. TrimEnd() - which isn't fully supported by EF subjectList = (from s in dbSubjects // TODO: replace hard-coded '&' with CommonCourseChar from .config where apiSubjects.Contains(s.CoursePrefixID.TrimEnd('&')) select new ScheduleCoursePrefix { Slug = s.Slug, Subject = s.CoursePrefixID, Title = s.Title }) .OrderBy(s => s.Title) .Distinct() .ToList(); } } return subjectList; }
/// <summary> /// /// </summary> /// <param name="db"></param> /// <param name="searchterm"></param> /// <param name="yrq"></param> /// <returns></returns> private SearchResultNoSectionModel GetNoSectionSearchResults(ClassScheduleDb db, string searchterm, YearQuarter yrq) { SqlParameter[] parms = { new SqlParameter("SearchWord", searchterm), new SqlParameter("YearQuarterID", yrq.ID) }; SearchResultNoSectionModel model = new SearchResultNoSectionModel {SearchedYearQuarter = yrq}; using (_profiler.Step("Executing 'other classes' stored procedure")) { model.NoSectionSearchResults = db.ExecuteStoreQuery<SearchResultNoSection>("usp_CourseSearch @SearchWord, @YearQuarterID", parms).ToList(); } return model; }
// // POST: /Api/Subjects /// <summary> /// Retrieves and updates Seats Available data for the specified <see cref="Section"/> /// </summary> /// <param name="classID"></param> /// <returns></returns> public ActionResult GetSeats(string classID) { int? seats = null; string friendlyTime = string.Empty; string itemNumber = classID.Substring(0, 4); string yrq = classID.Substring(4, 4); CourseHPQuery query = new CourseHPQuery(); int hpSeats = query.FindOpenSeats(itemNumber, yrq); using (ClassScheduleDb db = new ClassScheduleDb()) { //if the HP query didn't fail, save the changes. Otherwise, leave the SeatAvailability table alone. //This way, the correct number of seats can be pulled by the app and displayed instead of updating the //table with a 0 for seats available. if (hpSeats >= 0) { IQueryable<SectionSeat> seatsAvailableLocal = from s in db.SectionSeats where s.ClassID == classID select s; int rows = seatsAvailableLocal.Count(); if (rows > 0) { // TODO: Should only be updating one record //update the value foreach (SectionSeat seat in seatsAvailableLocal) { seat.SeatsAvailable = hpSeats; seat.LastUpdated = DateTime.Now; } } else { //insert the value SectionSeat newseat = new SectionSeat(); newseat.ClassID = classID; newseat.SeatsAvailable = hpSeats; newseat.LastUpdated = DateTime.Now; db.SectionSeats.AddObject(newseat); } db.SaveChanges(); } // retrieve updated seats data IQueryable<SectionSeat> seatsAvailable = from s in db.SectionSeats where s.ClassID == classID select s; SectionSeat newSeat = seatsAvailable.First(); seats = newSeat.SeatsAvailable; friendlyTime = newSeat.LastUpdated.GetValueOrDefault().ToString("h:mm tt").ToLower(); } string jsonReturnValue = string.Format("{0}|{1}", seats, friendlyTime); return Json(jsonReturnValue); }
public ActionResult UpdateSectionFootnote(string classId, string newFootnoteText) { bool result = false; // Trim any whitespace if (!String.IsNullOrEmpty(newFootnoteText)) { newFootnoteText = newFootnoteText.Trim(); } if (HttpContext.User.Identity.IsAuthenticated == true) { using (ClassScheduleDb db = new ClassScheduleDb()) { IQueryable<SectionsMeta> footnotes = db.SectionsMetas.Where(s => s.ClassID == classId); if (footnotes.Count() > 0) { // Should only update one section foreach (SectionsMeta footnote in footnotes) { if (!String.Equals(footnote.Footnote, newFootnoteText)) { footnote.Footnote = newFootnoteText; footnote.LastUpdated = DateTime.Now; //footnote.LastUpdatedBy result = true; } } } else if (classId != null && !String.IsNullOrWhiteSpace(newFootnoteText)) { // Insert footnote SectionsMeta newFootnote = new SectionsMeta(); newFootnote.ClassID = classId; newFootnote.Footnote = newFootnoteText; newFootnote.LastUpdated = DateTime.Now; db.SectionsMetas.AddObject(newFootnote); result = true; } db.SaveChanges(); } } return Json(new { result = result, footnote = newFootnoteText }); }
public ActionResult ClassEdit(FormCollection collection) { string referrer = collection["referrer"]; if (HttpContext.User.Identity.IsAuthenticated) { string courseId = collection["CourseID"]; string username = HttpContext.User.Identity.Name; string footnote = collection["Footnote"]; footnote = Helpers.StripHtml(footnote); if (ModelState.IsValid) { CourseMeta itemToUpdate; using (ClassScheduleDb db = new ClassScheduleDb()) { bool exists = db.CourseMetas.Any(c => c.CourseID == courseId); itemToUpdate = exists ? db.CourseMetas.Single(c => c.CourseID == courseId) : new CourseMeta(); itemToUpdate.CourseID = courseId; itemToUpdate.Footnote = footnote; itemToUpdate.LastUpdated = DateTime.Now; itemToUpdate.LastUpdatedBy = username; if (!exists) { db.CourseMetas.AddObject(itemToUpdate); } db.SaveChanges(); } } } return Redirect(referrer); }
public JsonResult CrossListedCourses(string courseID, string yearQuarterID) { // TODO: Validate incoming yearQuarterID value // see https://github.com/BellevueCollege/CtcApi/issues/8 if (yearQuarterID.Length != 4) { _log.Error(m => m("An invalid YearQuarterID was provided for looking up cross-listed sections: '{0}'", yearQuarterID)); } else { using (ClassScheduleDb db = new ClassScheduleDb()) { string[] classIDs = (from c in db.SectionCourseCrosslistings where c.CourseID == courseID && c.ClassID.EndsWith(yearQuarterID) select c.ClassID).ToArray(); if (!classIDs.Any()) { _log.Warn(m => m("No cross-listed Sections were found for Course '{0}' in '{1}'", courseID, yearQuarterID)); } else { int count = classIDs.Count(); _log.Trace(m => m("{0} cross-listed sections found for '{1}': [{2}]", count, courseID, classIDs.Mash())); // HACK: SectionID constructors are currently protected, so we have to manually create them IList<ISectionID> sectionIDs = new List<ISectionID>(count); foreach (string id in classIDs) { sectionIDs.Add(SectionID.FromString(id)); } // write a warning to the log if we've got too many courses cross-listed with this section if (_log.IsWarnEnabled && sectionIDs.Count > MAX_COURSE_CROSSLIST_WARNING_THRESHOLD) { _log.Warn(m => m("Cross-listing logic assumes a very small number of Sections will be cross-listed with any given Course, but is now being asked to process {0} Sections for '{1}'. (This warning triggers when more than {2} are detected.)", sectionIDs.Count, courseID, MAX_COURSE_CROSSLIST_WARNING_THRESHOLD)); } using (OdsRepository ods = new OdsRepository()) { IList<Section> odsSections = ods.GetSections(sectionIDs); IList<SectionWithSeats> classScheduleSections = Helpers.GetSectionsWithSeats(yearQuarterID, odsSections.ToList(), db); IList<CrossListedCourseModel> crosslistings = (from c in classScheduleSections select new CrossListedCourseModel { // BUG: API doesn't property notify CourseID in derived class CourseID = CourseID.FromString(c.CourseID), SectionID = c.ID, // HACK: Remove IsCommonCourse property when API is fixed (see above) IsCommonCourse = c.IsCommonCourse, Credits = c.Credits, IsVariableCredits = c.IsVariableCredits, Title = c.CourseTitle }).ToList(); // NOTE: AllowGet exposes the potential for JSON Hijacking (see http://haacked.com/archive/2009/06/25/json-hijacking.aspx) // but is not an issue here because we are receiving and returning public (e.g. non-sensitive) data return Json(crosslistings, JsonRequestBehavior.AllowGet); } } } } return Json(null, JsonRequestBehavior.AllowGet); }
/// <summary> /// Gathers a <see cref="Subject"/>'s related <see cref="Department"/>, <see cref="Division"/>, and <see cref="SubjectsCoursePrefix"/>s /// </summary> /// <param name="slug">The slug identifier for a given <see cref="Subject"/></param> /// <returns>An instance of <see cref="SubjectInfoResult"/> containing data related to the <see cref="Subject"/>, or null if the subject was not found.</returns> public static SubjectInfoResult GetSubjectInfo(string slug) { SubjectInfoResult result = null; using (ClassScheduleDb context = new ClassScheduleDb()) { Subject subject = GetSubject(slug, context); if (subject == null) { return result; } result = new SubjectInfoResult { Subject = subject, CoursePrefixes = subject.CoursePrefixes.ToList(), Department = subject.Department ?? new Department(), }; result.Division = result.Department.Division ?? new Division(); // If the url is a fully qualified url (e.g. http://continuinged.bellevuecollege.edu/about) // or empty just return it, otherwise prepend with the current school url. string deptUrl = result.Department.URL ?? string.Empty; if (!string.IsNullOrWhiteSpace(deptUrl) && !Regex.IsMatch(deptUrl, @"^https?://")) { result.Department.URL = deptUrl; } } return result; }
/// <summary> /// Takes a Subject, ClassNum and CourseID and finds the CMS Footnote for the course. /// </summary> /// <param name="courseID"></param> private string GetCmsFootnote(ICourseID courseID) { using (ClassScheduleDb db = new ClassScheduleDb()) { using (_profiler.Step("Getting app-specific Section records from DB")) { string fullCourseID = Helpers.BuildCourseID(courseID.Number, courseID.Subject.TrimEnd(), courseID.IsCommonCourse); CourseMeta item = db.CourseMetas.Where(s => s.CourseID.Trim().Equals(fullCourseID, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); if (item != null) { return item.Footnote; } } } return null; }
/// <summary> /// Groups a list of Sections by course, into descriptive SectionBlocks /// </summary> /// <param name="sections">List of sections to group</param> /// <param name="db"></param> /// <returns>List of SectionBlock objects which describe the block of sections</returns> public static IList<SectionsBlock> GroupSectionsIntoBlocks(IList<SectionWithSeats> sections, ClassScheduleDb db) { #if DEBUG /* COMMENT THIS LINE TO DEBUG if (sections.Any(s => s.CourseSubject.StartsWith("ENGL") && s.CourseNumber == "266")) { SectionWithSeats zengl266 = sections.Where(s => s.CourseSubject.StartsWith("ENGL") && s.CourseNumber == "266").First(); Debug.Print("\n{0} - {1} {2}\t[{4}]\t(Linked to: {3})\n", zengl266.ID, zengl266.CourseID, zengl266.CourseTitle, zengl266.LinkedTo, zengl266.IsLinked ? "LINKED" : string.Empty); } else { Debug.Print("\nENGL 266 - NOT FOUND AMONG SECTIONS.\n"); } // END DEBUGGING */ #endif MiniProfiler profiler = MiniProfiler.Current; IList<SectionsBlock> results = new List<SectionsBlock>(); //we need to captuer linked classes and exclude from the export: // Linked section defintion: An item number linking a class to another class in the next quarter. Automatic registration into the linked //class occurs in batch registration for students enrolled in the class containing the ITM-YRQ-LINK. IList<SectionWithSeats> allLinkedSections = sections.Where(s => s.IsLinked).ToList(); IList<SectionWithSeats> nonLinkedSections; // minimize trips to the database, since this collection should be relatively small IList<SectionCourseCrosslisting> crosslistings; using (profiler.Step("Retrieving Section/Course cross-listings")) { crosslistings = db.SectionCourseCrosslistings.ToList(); } // sort by the markers indicators where we need to being a new block (w/ course title, etc.) /* TODO: Implement a more configurable sort method. */ using (profiler.Step("Sorting sections in preparation for grouping and linking")) { #if DEBUG /* COMMENT THIS LINE TO DEBUG IEnumerable<SectionWithSeats> d1 = sections.Where(s => s.ID.ItemNumber.StartsWith("410")); // END DEBUGGING */ #endif nonLinkedSections = sections.Where(s => !s.IsLinked) .OrderBy(s => s.CourseNumber) .ThenBy(s => allLinkedSections.Where(l => l.LinkedTo == s.ID.ItemNumber).Count()) .ThenBy(s => s.CourseTitle) .ThenByDescending(s => s.IsVariableCredits) // johanna.aqui 9/11/2014 moved IsVariableCredits & Credits above starttime to keep correct section group .ThenBy(s => s.Credits) .ThenBy(s => s.IsOnline) .ThenBy(s => s.Offered.First().StartTime) .ThenBy(s => s.IsTelecourse) .ThenBy(s => s.IsHybrid) .ThenBy(s => s.IsOnCampus) .ThenBy(s => s.SectionCode).ToList(); //nonLinkedSections = sections.Where(s => !s.IsLinked) // .OrderBy(s => s.CourseNumber) // .ThenBy(s => allLinkedSections.Where(l => l.LinkedTo == s.ID.ItemNumber).Count()) // .ThenBy(s => s.CourseTitle) // .ThenByDescending(s => s.IsVariableCredits) // .ThenBy(s => s.Credits) // .ThenBy(s => s.IsTelecourse) // .ThenBy(s => s.IsOnline) // .ThenBy(s => s.IsHybrid) // .ThenBy(s => s.IsOnCampus) // .ThenBy(s => s.SectionCode).ToList(); } #if DEBUG /* COMMENT THIS LINE TO DEBUG foreach (SectionWithSeats zs in nonLinkedSections.Where(s => s.CourseSubject.StartsWith("ENGL") && s.CourseNumber == "101")) { Debug.Print("{0} - {1} {2}\t{3}\t(Linked sections: {4})", zs.ID, zs.CourseID, zs.CourseTitle, zs.IsLinked ? " [LINKED] " : string.Empty, allLinkedSections.Count(l => l.LinkedTo == zs.ID.ItemNumber)); } if (!allLinkedSections.Any(s => s.CourseSubject.StartsWith("ENGL") && s.CourseNumber == "266")) { Debug.Print("\nENGL 266 - NOT FOUND AMONG LINKED SECTIONS."); } else { SectionWithSeats zengl266 = allLinkedSections.Where(s => s.CourseSubject.StartsWith("ENGL") && s.CourseNumber == "266").First(); Debug.Print("\n{0} - {1} {2}\t(Linked to: {3})", zengl266.ID, zengl266.CourseID, zengl266.CourseTitle, zengl266.LinkedTo); } if (!allLinkedSections.Any(s => s.CourseSubject.StartsWith("ENGL") && s.CourseNumber == "246")) { Debug.Print("\nENGL& 246 - NOT FOUND AMONG LINKED SECTIONS."); } else { SectionWithSeats zengl246 = allLinkedSections.Where(s => s.CourseSubject.StartsWith("ENGL") && s.CourseNumber == "246").First(); Debug.Print("\n{0} - {1} {2}\t(Linked to: {3})", zengl246.ID, zengl246.CourseID, zengl246.CourseTitle, zengl246.LinkedTo); } // END DEBUGGING */ #endif // Group all the sections into course blocks and determine which sections linked using (profiler.Step("Grouping/linking by course")) { int processedCount = 0; while (processedCount < nonLinkedSections.Count) { SectionsBlock courseBlock = new SectionsBlock(); courseBlock.LinkedSections = new List<SectionWithSeats>(); IList<SectionWithSeats> remainingSections = nonLinkedSections.Skip(processedCount).ToList(); SectionWithSeats firstSection = remainingSections.First(); // TODO: Replace BuildCourseID() with this logic - and pull in CommonCourceChar from .config string blockCourseID = String.Format("{0}{1} {2}", firstSection.CourseSubject, firstSection.IsCommonCourse ? "&" : String.Empty, firstSection.CourseNumber); if (allLinkedSections.Any(l => l.LinkedTo == firstSection.ID.ItemNumber)) { // If a Section has other Sections linked to it, then it should be displayed in its own block courseBlock.Sections = new List<SectionWithSeats> {firstSection}; } else { courseBlock.Sections = remainingSections.TakeWhile(s => s.CourseID == firstSection.CourseID && s.CourseTitle == firstSection.CourseTitle && s.Credits == firstSection.Credits && s.IsVariableCredits == firstSection.IsVariableCredits && allLinkedSections.All(l => l.LinkedTo != s.ID.ItemNumber) ).ToList(); } #if DEBUG /* COMMENT THIS LINE TO DEBUG // use the following variable as a conditional for breakpoints bool foo = courseBlock.Sections.Any(s => s.CourseSubject == "ACCT"); // END DEBUGGING */ /* COMMENT THIS LINE TO DEBUG Debug.Print("\nProcessing block: {0} - {1} {2}\t{3}\t(Crosslinks: {4})", firstSection.ID, firstSection.CourseID, firstSection.IsCommonCourse ? "(&)" : string.Empty, firstSection.CourseTitle, crosslistings.Count(x => x.CourseID == blockCourseID)); // END DEBUGGING */ #endif // Flag whether or not this course block is crosslisted with other sections offered in the same quarter courseBlock.IsCrosslisted = crosslistings.Any(x => x.CourseID == blockCourseID && x.ClassID.EndsWith(firstSection.Yrq.ID)); // Find all links associated to each of the grouped sections foreach (SectionWithSeats sec in courseBlock.Sections) { SectionWithSeats sect = sec; // Use copy of object to ensure cross-compiler compatibility List<SectionWithSeats> linkedSections = allLinkedSections.Where(s => sect != null && s.LinkedTo == sect.ID.ItemNumber).ToList(); if (linkedSections.Count > 0) { courseBlock.LinkedSections.AddRange(linkedSections); } } // Get a list of common footnotes shared with every section in the block courseBlock.CommonFootnotes = ExtractCommonFootnotes(courseBlock.Sections); processedCount += courseBlock.Sections.Count(); results.Add(courseBlock); } } return results; }
/// <summary> /// Common method to retrieve <see cref="SectionWithSeats"/> records /// </summary> /// <param name="currentYrq"></param> /// <param name="sections"></param> /// <param name="db"></param> /// <returns></returns> public static IList<SectionWithSeats> GetSectionsWithSeats(string currentYrq, IList<Section> sections, ClassScheduleDb db) { MiniProfiler profiler = MiniProfiler.Current; // ensure we're ALWAYS getting the latest data from the database (i.e. ignore any cached data) // Reference: http://forums.asp.net/post/2848021.aspx db.vw_Class.MergeOption = MergeOption.OverwriteChanges; IList<vw_Class> classes; using (profiler.Step("API::Get Class Schedule Specific Data()")) { classes = db.vw_Class.Where(c => c.YearQuarterID == currentYrq).ToList(); } IList<SectionWithSeats> sectionsEnum; using (profiler.Step("Joining all data")) { sectionsEnum = ( from c in sections join d in classes on c.ID.ToString() equals d.ClassID into t1 from d in t1.DefaultIfEmpty() join ss in db.SectionSeats on (d != null ? d.ClassID : "") equals ss.ClassID into t2 from ss in t2.DefaultIfEmpty() join sm in db.SectionsMetas on (d != null ? d.ClassID : "") equals sm.ClassID into t3 from sm in t3.DefaultIfEmpty() // NOTE: This logic assumes that data will only be saved in ClassScheduleDb after having come through // the filter of the CtcApi - which normalizes spacing of the CourseID field data. join cm in db.CourseMetas on (d != null ? d.CourseID : "") equals cm.CourseID into t4 from cm in t4.DefaultIfEmpty() orderby c.Credits ,c.Offered.First().StartTime, c.Yrq.ID descending //9/15/2014 johanna.aqui, added credit and start time to sort order. Order applied at different times and has different effects depending on the controller (Search, Classes, Scheduler) select new SectionWithSeats { ParentObject = c, SeatsAvailable = ss != null ? ss.SeatsAvailable : Int32.MinValue, // allows us to identify past quarters (with no availability info) SeatsLastUpdated = (ss != null ? ss.LastUpdated.GetValueOrDefault() : DateTime.MinValue).ToString(Settings.Default.SeatUpdatedDateTimeFormat).ToLower(), LastUpdated = (d != null ? d.LastUpdated.GetValueOrDefault() : DateTime.MinValue).ToString("h:mm tt").ToLower(), SectionFootnotes = sm != null && !String.IsNullOrWhiteSpace(sm.Footnote) ? sm.Footnote : String.Empty, CourseFootnotes = cm != null && !String.IsNullOrWhiteSpace(cm.Footnote) ? cm.Footnote : String.Empty, CourseTitle = sm != null && !String.IsNullOrWhiteSpace(sm.Title) ? sm.Title : c.CourseTitle, CustomDescription = sm != null && !String.IsNullOrWhiteSpace(sm.Description) ? sm.Description : String.Empty, }).OrderBy(s => s.CourseNumber).ThenBy(s => s.CourseTitle).ToList(); string _availValue = ""; if (HttpContext.Current.Request.QueryString["avail"] != null) { _availValue = HttpContext.Current.Request.QueryString["avail"].ToString(); } if (_availValue == "Open") { sectionsEnum = (from open in sectionsEnum where open.SeatsAvailable != 0 select open).ToList(); } #if DEBUG /* COMMENT THIS LINE TO DEBUG if (sectionsEnum.Any(s => s.CourseSubject.StartsWith("ACCT") && s.CourseNumber == "203" && s.IsCommonCourse)) { SectionWithSeats zSec = sectionsEnum.Where(s => s.CourseSubject.StartsWith("ACCT") && s.CourseNumber == "202" && s.IsCommonCourse).First(); string s1 = zSec.ID.ToString(); Debug.Print("\n{0} - {1} {2}\t{3}\t(Crosslinks: {4})\n", zSec.ID, zSec.CourseID, zSec.IsCommonCourse ? "(&)" : string.Empty, zSec.CourseTitle, db.SectionCourseCrosslistings.Select(x => x.ClassID).Distinct().Count(x => x == s1)); } else { Debug.Print("\nACCT& 202 - NOT FOUND AMONG SECTIONS.\n"); } // END DEBUGGING */ #endif // Flag sections that are cross-linked with a Course foreach (string sec in db.SectionCourseCrosslistings.Select(x => x.ClassID).Distinct()) { if (sectionsEnum.Any(s => s.ID.ToString() == sec)) { sectionsEnum.Single(s => s.ID.ToString() == sec).IsCrossListed = true; } } } return sectionsEnum; }
/// <summary> /// Gathers a List of <see cref="SubjectsCoursePrefix"/> based on the <see cref="Subject"/>'s <paramref name="slug"/> identifier /// </summary> /// <param name="slug">The slug identifier for a given <see cref="Subject"/></param> /// <returns>A List of all <see cref="SubjectsCoursePrefix"/>s located for the given <see cref="Subject"/></returns> public static IList<string> GetSubjectPrefixes(string slug) { IList<string> result = new List<string>(); using (ClassScheduleDb context = new ClassScheduleDb()) { Subject subject = GetSubject(slug, context); if (subject == null) { // if we didn't find a custom subject relationship, use the URL slug result.Add(slug); } else { result = subject.CoursePrefixes.Select(p => p.CoursePrefixID).ToList(); } } return result; }
/// <summary> /// Gathers a <see cref="Subject"/>'s related <see cref="Department"/>, <see cref="Division"/>, and <see cref="SubjectsCoursePrefix"/>s /// </summary> /// <param name="prefix">The prefix identifier for a given <see cref="Subject"/></param> /// <returns>An instance of <see cref="SubjectInfoResult"/> containing data related to the <see cref="Subject"/>, or null if the subject was not found.</returns> public static SubjectInfoResult GetSubjectInfoFromPrefix(string prefix) { SubjectInfoResult result = null; using (ClassScheduleDb context = new ClassScheduleDb()) { Subject subject = (from s in context.Subjects join p in context.SubjectsCoursePrefixes on s.SubjectID equals p.SubjectID into j from sub in j where sub.CoursePrefixID == prefix select s).FirstOrDefault(); if (subject == null) { _log.Warn(m => m("Failed to retrieve Subject record for PrefixID '{0}'", prefix)); return result; } result = new SubjectInfoResult { Subject = subject, CoursePrefixes = subject.CoursePrefixes.ToList(), Department = subject.Department ?? new Department(), }; result.Division = result.Department.Division ?? new Division(); // If the url is a fully qualified url (e.g. http://continuinged.bellevuecollege.edu/about) // or empty just return it, otherwise prepend with the current school url. string deptUrl = result.Department.URL ?? string.Empty; if (!string.IsNullOrWhiteSpace(deptUrl) && !Regex.IsMatch(deptUrl, @"^https?://")) { result.Department.URL = deptUrl; } } return result; }
public ActionResult YearQuarter(String YearQuarter, string timestart, string timeend, string day_su, string day_m, string day_t, string day_w, string day_th, string day_f, string day_s, string f_oncampus, string f_online, string f_hybrid, string avail, string letter, string latestart, string numcredits, string format) { _log.Trace(m => m("Calling: [.../classes/{0}...] From (referrer): [{1}]", YearQuarter, Request.UrlReferrer)); // TODO: come up with a better way to maintain various State flags ViewBag.Modality = Helpers.ConstructModalityList(f_oncampus, f_online, f_hybrid); ViewBag.Days = Helpers.ConstructDaysList(day_su, day_m, day_t, day_w, day_th, day_f, day_s); ViewBag.LinkParams = Helpers.getLinkParams(Request); ViewBag.timestart = timestart; ViewBag.timeend = timeend; ViewBag.latestart = latestart; ViewBag.avail = avail; IList<ISectionFacet> facets = Helpers.addFacets(timestart, timeend, day_su, day_m, day_t, day_w, day_th, day_f, day_s, f_oncampus, f_online, f_hybrid, avail, latestart, numcredits); YearQuarterModel model = new YearQuarterModel { ViewingSubjects = new List<SubjectModel>(), SubjectLetters = new List<char>(), }; try { using (OdsRepository repository = new OdsRepository(HttpContext)) { YearQuarter yrq = Helpers.DetermineRegistrationQuarter(YearQuarter, repository.CurrentRegistrationQuarter, RouteData); model.ViewingQuarter = yrq; model.NavigationQuarters = Helpers.GetYearQuarterListForMenus(repository); // set up all the ancillary data we'll need to display the View SetCommonViewBagVars(repository, avail, letter); // TODO: Refactor the following code to use ApiController.GetSubjectList() // after reconciling the noted differences between AllClasses() and YearQuarter() - 4/27/2012, [email protected] using (ClassScheduleDb db = new ClassScheduleDb()) { // Compile a list of active subjects char[] commonCourseChar = _apiSettings.RegexPatterns.CommonCourseChar.ToCharArray(); IList<string> activePrefixes = repository.GetCourseSubjects(yrq, facets).Select(p => p.Subject).ToList(); IList<Subject> subjects = new List<Subject>(); // NOTE: Unable to reduce the following loop to a LINQ statement because it complains about not being able to use TrimEnd(char[]) w/ LINQ-to-Entities // (Although it appears to be doing so just fine in the if statement below). - [email protected] foreach (Subject sub in db.Subjects) { // TODO: whether the CoursePrefix has active courses or not, any Prefix with a '&' will be included // because GetCourseSubjects() does not include the common course char. if (sub.CoursePrefixes.Select(sp => sp.CoursePrefixID).Any(sp => activePrefixes.Contains(sp.TrimEnd(commonCourseChar)))) { subjects.Add(sub); } } model.SubjectLetters = subjects.Select(s => s.Title.First()).Distinct().ToList(); model.ViewingSubjects = (letter != null ? subjects.Where(s => s.Title.StartsWith(letter, StringComparison.OrdinalIgnoreCase)).Distinct() : subjects ).Select(s => new SubjectModel { Title = s.Title, Slug = s.Slug, CoursePrefixes = s.CoursePrefixes.Select(p => p.CoursePrefixID).ToList() } ).ToList(); if (format == "json") { // NOTE: AllowGet exposes the potential for JSON Hijacking (see http://haacked.com/archive/2009/06/25/json-hijacking.aspx) // but is not an issue here because we are receiving and returning public (e.g. non-sensitive) data JsonResult json = Json(model, JsonRequestBehavior.AllowGet); return json; } return View(model); } } } catch (ArgumentOutOfRangeException ex) { if (ex.Message.ToUpper().Contains("MUST BE A VALID QUARTER TITLE")) { throw new HttpException(404, string.Format("'{0}' is not a recognized Quarter.", YearQuarter), ex); } _log.Error(m => m("An unhandled ArgumentOutOfRangeException ocurred, returning an empty Model to the YearQuarter view."), ex); } catch (Exception ex) { _log.Error(m => m("An unhandled exception occurred, returning an empty Model to the YearQuarter view."), ex); } // empty model return View(model); }
/// <summary> /// /// </summary> /// <param name="db"></param> /// <param name="searchterm"></param> /// <param name="quarter"></param> /// <returns></returns> private IList<SearchResult> GetSearchResults(ClassScheduleDb db, string searchterm, string quarter) { SqlParameter[] parms = { new SqlParameter("SearchWord", searchterm), new SqlParameter("YearQuarterID", YearQuarter.ToYearQuarterID(quarter)) }; using (_profiler.Step("Executing search stored procedure")) { return db.ExecuteStoreQuery<SearchResult>("usp_ClassSearch @SearchWord, @YearQuarterID", parms).ToList(); } }
public ActionResult ClassEdit(string CourseNumber, string Subject, bool IsCommonCourse) { if (HttpContext.User.Identity.IsAuthenticated) { string fullCourseId = Helpers.BuildCourseID(CourseNumber, Subject, IsCommonCourse); ICourseID courseID = CourseID.FromString(fullCourseId); CourseMeta itemToUpdate = null; var hpFootnotes = string.Empty; string courseTitle = string.Empty; using (ClassScheduleDb db = new ClassScheduleDb()) { if(db.CourseMetas.Any(s => s.CourseID.Trim().ToUpper() == fullCourseId.ToUpper())) { //itemToUpdate = db.CourseFootnotes.Single(s => s.CourseID.Substring(0, 5).Trim().ToUpper() == subject.Trim().ToUpper() && // s.CourseID.Trim().EndsWith(courseID.Number) itemToUpdate = db.CourseMetas.Single(s => s.CourseID.Trim().ToUpper() == fullCourseId.ToUpper()); } using (OdsRepository repository = new OdsRepository()) { try { IList<Course> coursesEnum = repository.GetCourses(courseID); foreach (Course course in coursesEnum) { hpFootnotes = course.Footnotes.ToArray().Mash(" "); // BUG: If more than one course is returned from the API, this will ignore all Titles except for the last one courseTitle = course.Title; } } catch(InvalidOperationException ex) { _log.Warn(m => m("Ignoring Exception while attempting to retrieve footnote and title data for CourseID '{0}'\n{1}", courseID, ex)); } } ClassFootnote localClass = new ClassFootnote(); localClass.CourseID = MvcApplication.SafePropertyToString(itemToUpdate, "CourseID", fullCourseId); localClass.Footnote = MvcApplication.SafePropertyToString(itemToUpdate, "Footnote", string.Empty); localClass.HPFootnote = hpFootnotes; localClass.LastUpdated = MvcApplication.SafePropertyToString(itemToUpdate, "LastUpdated", string.Empty); localClass.LastUpdatedBy = MvcApplication.SafePropertyToString(itemToUpdate, "LastUpdatedBy", string.Empty); localClass.CourseTitle = courseTitle; return PartialView(localClass); } } return PartialView(); }
// // GET: /Search/ public ActionResult Index(string searchterm, string Subject, string quarter, string timestart, string timeend, string day_su, string day_m, string day_t, string day_w, string day_th, string day_f, string day_s, string f_oncampus, string f_online, string f_hybrid, string avail, string latestart, string numcredits, int p_offset = 0) { // We don't currently support quoted phrases. - 4/19/2012, [email protected] searchterm = searchterm.Replace("\"", string.Empty); // TODO: This needs to be configurable if (quarter == "CE") { Response.Redirect("http://www.campusce.net/BC/Search/Search.aspx?q=" + searchterm, true); return null; } if (String.IsNullOrEmpty(searchterm.Trim())) { return RedirectToAction("AllClasses", "Classes", new { YearQuarterID = quarter }); } ViewBag.timestart = timestart; ViewBag.timeend = timeend; ViewBag.avail = avail; ViewBag.Modality = Helpers.ConstructModalityList(f_oncampus, f_online, f_hybrid); ViewBag.Days = Helpers.ConstructDaysList(day_su, day_m, day_t, day_w, day_th, day_f, day_s); ViewBag.Subject = Subject; ViewBag.searchterm = Regex.Replace(searchterm, @"\s+", " "); // replace each clump of whitespace w/ a single space (so the database can better handle it) ViewBag.ErrorMsg = string.Empty; IList<ISectionFacet> facets = Helpers.addFacets(timestart, timeend, day_su, day_m, day_t, day_w, day_th, day_f, day_s, f_oncampus, f_online, f_hybrid, avail, latestart, numcredits); ViewBag.LinkParams = Helpers.getLinkParams(Request, "submit"); using (OdsRepository repository = new OdsRepository()) { YearQuarter yrq = string.IsNullOrWhiteSpace(quarter) ? repository.CurrentYearQuarter : YearQuarter.FromFriendlyName(quarter); IList<YearQuarter> menuQuarters = Helpers.GetYearQuarterListForMenus(repository); QuarterNavigationModel quarterNavigation = new QuarterNavigationModel { NavigationQuarters = menuQuarters, CurrentQuarter = menuQuarters[0], ViewingQuarter = yrq, }; IList<Section> sections; using (_profiler.Step("API::GetSections()")) { if (string.IsNullOrWhiteSpace(Subject)) { sections = repository.GetSections(yrq, facets); } else { IList<string> prefixes = SubjectInfo.GetSubjectPrefixes(Subject); sections = repository.GetSections(prefixes, yrq, facets); } } int currentPage; int totalPages; int itemCount; IList<SectionWithSeats> sectionsEnum; IList<SearchResult> searchResults; SearchResultNoSectionModel noSectionSearchResults; IList<SectionsBlock> courseBlocks; using (ClassScheduleDb db = new ClassScheduleDb()) { searchResults = GetSearchResults(db, searchterm, quarter); noSectionSearchResults = GetNoSectionSearchResults(db, searchterm, yrq); sections = (from s in sections join r in searchResults on s.ID.ToString() equals r.ClassID select s).ToList(); sectionsEnum = Helpers.GetSectionsWithSeats(yrq.ID, sections, db); // do not count Linked sections (since we don't display them) itemCount = sectionsEnum.Count(s => !s.IsLinked); totalPages = (int)Math.Round((itemCount / ITEMS_PER_PAGE) + 0.5); currentPage = p_offset + 1; using (_profiler.Step("Getting just records for page")) { if (currentPage > totalPages && totalPages > 0) { currentPage = totalPages; } sectionsEnum = sectionsEnum.Skip(p_offset * ITEMS_PER_PAGE).Take(ITEMS_PER_PAGE).ToList(); } courseBlocks = Helpers.GroupSectionsIntoBlocks(sectionsEnum, db); } IEnumerable<string> allSubjects; using (_profiler.Step("Getting distinct list of subjects")) { allSubjects = sectionsEnum.Select(c => c.CourseSubject).Distinct().OrderBy(c => c); } SearchResultsModel model = new SearchResultsModel { ItemCount = itemCount, TotalPages = totalPages, CurrentPage = currentPage, Courses = courseBlocks, SearchResultNoSection = noSectionSearchResults, AllSubjects = allSubjects, QuarterNavigation = quarterNavigation, }; return View(model); } }
public ActionResult YearQuarterSubject(String YearQuarter, string Subject, string timestart, string timeend, string day_su, string day_m, string day_t, string day_w, string day_th, string day_f, string day_s, string f_oncampus, string f_online, string f_hybrid, string avail, string latestart, string numcredits, string format) { IList<ISectionFacet> facets = Helpers.addFacets(timestart, timeend, day_su, day_m, day_t, day_w, day_th, day_f, day_s, f_oncampus, f_online, f_hybrid, avail, latestart, numcredits); using (OdsRepository repository = new OdsRepository()) { YearQuarter yrq = Helpers.DetermineRegistrationQuarter(YearQuarter, repository.CurrentRegistrationQuarter, RouteData); // Get the courses to display on the View IList<Section> sections; using (_profiler.Step("ODSAPI::GetSections()")) { IList<string> prefixes = SubjectInfo.GetSubjectPrefixes(Subject); sections = repository.GetSections(prefixes, yrq, facets); } #if DEBUG /* COMMENT THIS LINE FOR DEBUGGING/TROUBLESHOOTING Debug.Print("==> sections"); string zItemNum = "1230", zYrq = "B234"; // ENGL 266 if (sections.Any(s => s.ID.ItemNumber == zItemNum && s.ID.YearQuarter == zYrq)) { Section foo = sections.Where(s => s.ID.ItemNumber == zItemNum && s.ID.YearQuarter == zYrq).First(); Debug.Print("\n{0} - {1} {2}\t(Linked to: {3})", foo.ID, foo.CourseID, foo.CourseTitle, foo.LinkedTo); } else { Debug.Print("ClassID '{0}{1}' not found.", zItemNum, zYrq); } // */ #endif IList<SectionsBlock> courseBlocks; using (ClassScheduleDb db = new ClassScheduleDb()) { IList<SectionWithSeats> sectionsEnum; using (_profiler.Step("Getting app-specific Section records from DB")) { sectionsEnum = Helpers.GetSectionsWithSeats(yrq.ID, sections, db); } #if DEBUG /* COMMENT THIS LINE FOR DEBUGGING/TROUBLESHOOTING Debug.Print("==> sectionsEnum"); // string zItemNum, zYrq; zItemNum = "1230"; zYrq = "B234"; // ENGL 266 if (sectionsEnum.Any(s => s.ID.ItemNumber == zItemNum && s.ID.YearQuarter == zYrq)) { SectionWithSeats foo = sectionsEnum.Where(s => s.ID.ItemNumber == zItemNum && s.ID.YearQuarter == zYrq).First(); Debug.Print("\n{0} - {1} {2}\t(Linked to: {3})", foo.ID, foo.CourseID, foo.CourseTitle, foo.LinkedTo); } else { Debug.Print("ClassID '{0}{1}' not found.", zItemNum, zYrq); } // */ /* COMMENT THIS LINE FOR DEBUGGING/TROUBLESHOOTING IEnumerable<SectionWithSeats> s1 = sectionsEnum.Where(s => s.CourseSubject == "ENGL" && s.CourseNumber == "239"); // */ #endif courseBlocks = Helpers.GroupSectionsIntoBlocks(sectionsEnum, db); } // Construct the model SubjectInfoResult subject = SubjectInfo.GetSubjectInfoFromPrefix(Subject); IList<YearQuarter> yrqRange = Helpers.GetYearQuarterListForMenus(repository); YearQuarterSubjectModel model = new YearQuarterSubjectModel { Courses = courseBlocks, CurrentQuarter = repository.CurrentYearQuarter, CurrentRegistrationQuarter = yrqRange[0], NavigationQuarters = yrqRange, ViewingQuarter = yrq, // if we were unable to determine a Slug, use the Subject (e.g. Prefix) that brought us here Slug = subject != null ? subject.Subject.Slug : Subject, SubjectTitle = subject != null ? subject.Subject.Title : string.Empty, SubjectIntro = subject != null ? subject.Subject.Intro : string.Empty, DepartmentTitle = subject != null ? subject.Department.Title : string.Empty, DepartmentURL = subject != null ? subject.Department.URL : string.Empty, }; if (format == "json") { // NOTE: AllowGet exposes the potential for JSON Hijacking (see http://haacked.com/archive/2009/06/25/json-hijacking.aspx) // but is not an issue here because we are receiving and returning public (e.g. non-sensitive) data return Json(model, JsonRequestBehavior.AllowGet); } // set up all the ancillary data we'll need to display the View ViewBag.timestart = timestart; ViewBag.timeend = timeend; ViewBag.avail = avail; ViewBag.latestart = latestart; ViewBag.Modality = Helpers.ConstructModalityList(f_oncampus, f_online, f_hybrid); ViewBag.Days = Helpers.ConstructDaysList(day_su, day_m, day_t, day_w, day_th, day_f, day_s); ViewBag.LinkParams = Helpers.getLinkParams(Request); SetCommonViewBagVars(repository, avail, string.Empty); // TODO: Add query string info (e.g. facets) to the routeValues dictionary so we can pass it all as one chunk. IDictionary<string, object> routeValues = new Dictionary<string, object>(3); routeValues.Add("YearQuarterID", YearQuarter); ViewBag.RouteValues = routeValues; return View(model); } }