diff --git a/app/django/timetables/management/commands/exportcsv.py b/app/django/timetables/management/commands/exportcsv.py index 7658109..13996ce 100644 --- a/app/django/timetables/management/commands/exportcsv.py +++ b/app/django/timetables/management/commands/exportcsv.py @@ -5,6 +5,7 @@ import csv import sys import argparse +import collections import pytz @@ -84,15 +85,17 @@ def export_to_csv_writer(self, csv_writer): for event in self.events: try: - self.write_row(csv_writer, self.get_row(event)) + for event_path in paths_to(event): + self.write_row(csv_writer, self.get_row(event_path)) except InvalidStructureException as e: print >> sys.stderr, "Skipping event", event.pk, e def get_header_row(self): return [spec.get_column_name() for spec in self.columns] - def get_row(self, event): - return [spec.extract_value(event) for spec in self.columns] + def get_row(self, event_path): + assert isinstance(event_path, EventPath) + return [spec.extract_value(event_path) for spec in self.columns] def write_row(self, csv_writer, row): for f in self.filters: @@ -101,6 +104,43 @@ def write_row(self, csv_writer, row): csv_writer.writerow(row) + +EventPath = collections.namedtuple( + "EventPath", "tripos part subpart module series event".split()) + + +def paths_to(event): + """ + Get the possible paths through the Thing tree to an Event + + An iterable of EventPaths is returned representing each way + an Event can be reached from the root. There's usually a single + path, but when a series is linked to more than one module an Event + in the series will have > 1 way to access it from the root. + """ + series_traverser = EventTraverser(event).step_up() + for module_traverser in series_traverser.walk_parents(): + mod_parent_traverser = module_traverser.step_up() + + if mod_parent_traverser.name == SubpartTraverser.name: + subpart = mod_parent_traverser.get_value() + part_traverser = mod_parent_traverser.step_up() + else: + subpart = None + assert mod_parent_traverser.name == PartTraverser.name + part_traverser = mod_parent_traverser + + tripos_traverser = part_traverser.step_up() + + yield EventPath( + tripos_traverser.get_value(), + part_traverser.get_value(), + subpart, + module_traverser.get_value(), + series_traverser.get_value(), + event) + + class UnicodeEncodeRowFilter(object): encoding = "utf-8" @@ -125,109 +165,97 @@ def __init__(self, name=None): def get_column_name(self): return self.name - def extract_value(self, event): + def extract_value(self, event_path): raise NotImplementedError() - def get_series(self, event): - return EventTraverser(event).step_up().get_value() - - def get_module(self, event): - series = self.get_series(event) - return SeriesTraverser(series).step_up().get_value() + def get_event(self, event_path): + return event_path.event - def get_subpart(self, event): - module = self.get_module(event) - traverser = ModuleTraverser(module).step_up() + def get_series(self, event_path): + return event_path.series - if traverser.name == SubpartTraverser.name: - return traverser.get_value() - return None + def get_module(self, event_path): + return event_path.module - def get_part(self, event): - subpart = self.get_subpart(event) - if subpart is not None: - traverser = SubpartTraverser(subpart) - else: - traverser = ModuleTraverser(self.get_module(event)) + def get_subpart(self, event_path): + return event_path.subpart - part_traverser = traverser.step_up() - assert part_traverser.name == PartTraverser.name - return part_traverser.get_value() + def get_part(self, event_path): + return event_path.part - def get_tripos(self, event): - part = self.get_part(event) - return PartTraverser(part).step_up().get_value() + def get_tripos(self, event_path): + return event_path.tripos class TriposNameColumnSpec(ColumnSpec): name = "Tripos Name" - def extract_value(self, event): - tripos = self.get_tripos(event) + def extract_value(self, event_path): + tripos = self.get_tripos(event_path) return tripos.fullname class TriposShortNameColumnSpec(ColumnSpec): name = "Tripos Short Name" - def extract_value(self, event): - tripos = self.get_tripos(event) + def extract_value(self, event_path): + tripos = self.get_tripos(event_path) return tripos.name class PartNameColumnSpec(ColumnSpec): name = "Part Name" - def extract_value(self, event): - part = self.get_part(event) + def extract_value(self, event_path): + part = self.get_part(event_path) return part.fullname class PartShortNameColumnSpec(ColumnSpec): name = "Part Short Name" - def extract_value(self, event): - part = self.get_part(event) + def extract_value(self, event_path): + part = self.get_part(event_path) return part.name class SubPartNameColumnSpec(ColumnSpec): name = "Subpart Name" - def extract_value(self, event): - subpart = self.get_subpart(event) + def extract_value(self, event_path): + subpart = self.get_subpart(event_path) return None if subpart is None else subpart.fullname class SubPartShortNameColumnSpec(ColumnSpec): name = "Subpart Short Name" - def extract_value(self, event): - subpart = self.get_subpart(event) + def extract_value(self, event_path): + subpart = self.get_subpart(event_path) return None if subpart is None else subpart.name class ModuleNameColumnSpec(ColumnSpec): name = "Module Name" - def extract_value(self, event): - module = self.get_module(event) + def extract_value(self, event_path): + module = self.get_module(event_path) return module.fullname class ModuleShortNameColumnSpec(ColumnSpec): name = "Module Short Name" - def extract_value(self, event): - module = self.get_module(event) + def extract_value(self, event_path): + module = self.get_module(event_path) return module.name class SeriesNameColumnSpec(ColumnSpec): name = "Series Name" - def extract_value(self, event): - series = self.get_series(event) + def extract_value(self, event_path): + series = self.get_series(event_path) return series.title @@ -238,8 +266,8 @@ def get_attr_name(self): assert self.attr_name is not None return self.attr_name - def extract_value(self, event): - return getattr(event, self.get_attr_name()) + def extract_value(self, event_path): + return getattr(self.get_event(event_path), self.get_attr_name()) class EventTitleColumnSpec(EventAttrColumnSpec): @@ -255,8 +283,8 @@ class EventLocationColumnSpec(EventAttrColumnSpec): class EventUidColumnSpec(ColumnSpec): name = "UID" - def extract_value(self, event): - return event.get_ical_uid() + def extract_value(self, event_path): + return self.get_event(event_path).get_ical_uid() class EventDateTimeColumnSpec(ColumnSpec): @@ -265,8 +293,8 @@ class EventDateTimeColumnSpec(ColumnSpec): def get_datetime_utc(self, event): raise NotImplementedError() - def extract_value(self, event): - dt_utc = self.get_datetime_utc(event) + def extract_value(self, event_path): + dt_utc = self.get_datetime_utc(self.get_event(event_path)) return self.timezone.normalize(dt_utc.astimezone(self.timezone)).isoformat() @@ -292,8 +320,8 @@ def get_metadata_path(self): raise ValueError("no metadata_path value provided") return self.metadata_path - def extract_value(self, event): - metadata = event.metadata + def extract_value(self, event_path): + metadata = self.get_event(event_path).metadata segments = self.get_metadata_path().split(".") for i, segment in enumerate(segments): @@ -313,6 +341,6 @@ class EventLecturerColumnSpec(EventMetadataColumnSpec): name = "People" metadata_path = "people" - def extract_value(self, event): - value = super(EventLecturerColumnSpec, self).extract_value(event) + def extract_value(self, event_path): + value = super(EventLecturerColumnSpec, self).extract_value(event_path) return None if value is None else ", ".join(value) diff --git a/app/django/timetables/management/commands/grasshopper_export_data.py b/app/django/timetables/management/commands/grasshopper_export_data.py new file mode 100644 index 0000000..1ac7549 --- /dev/null +++ b/app/django/timetables/management/commands/grasshopper_export_data.py @@ -0,0 +1,131 @@ +""" +Copy this file to: + app/django/timetables/management/commands/grasshopper_export_data.py + +Change directory so your current working directory is: + app/django + +Execute: + python manage.py grasshopper_export_data > data_dump.csv + +Depending on your machine, this can take a couple of minutes. You should end up with a CSV file +that contains all the necessary data for each event to build up a tree. + + +Export all events in the system into CSV format. Events with dodgy +ancestors (invalid data) are skipped. +""" +import csv +import sys +import argparse + +from timetables.management.commands import exportcsv + + +class Command(exportcsv.Command): + + def __init__(self): + super(exportcsv.Command, self).__init__() + + self.parser = argparse.ArgumentParser( + prog="grasshopper_export_events", + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + + def handle(self, args): + events = self.get_events() + + exporter = CsvExporter( + self.get_columns(), + [StripNewlinesRowFilter(), exportcsv.UnicodeEncodeRowFilter()], + events + ) + + exporter.export_to_stream(sys.stdout) + + def get_columns(self): + return [ + TriposIdColumnSpec(), exportcsv.TriposNameColumnSpec(), + PartIdColumnSpec(), exportcsv.PartNameColumnSpec(), + SubPartIdColumnSpec(), exportcsv.SubPartNameColumnSpec(), + ModuleIdColumnSpec(), exportcsv.ModuleNameColumnSpec(), + SeriesIdColumnSpec(), exportcsv.SeriesNameColumnSpec(), + EventIdColumnSpec(), exportcsv.EventTitleColumnSpec(), + exportcsv.EventTypeColumnSpec(), + exportcsv.EventStartDateTimeColumnSpec(), + exportcsv.EventEndDateTimeColumnSpec(), + exportcsv.EventLocationColumnSpec(), + EventLecturerColumnSpec() + ] + + +class CsvExporter(exportcsv.CsvExporter): + + def export_to_stream(self, dest): + csv_writer = csv.writer(dest, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL) + return self.export_to_csv_writer(csv_writer) + + +class StripNewlinesRowFilter(object): + def strip_newlines(self, val): + if isinstance(val, basestring): + return val.replace("\n", "") + return val + + def filter(self, row): + return [self.strip_newlines(val) for val in row] + + +class TriposIdColumnSpec(exportcsv.ColumnSpec): + name = "Tripos Id" + + def extract_value(self, event_path): + tripos = self.get_tripos(event_path) + return tripos.id + + +class PartIdColumnSpec(exportcsv.ColumnSpec): + name = "Part Id" + + def extract_value(self, event_path): + part = self.get_part(event_path) + return part.id + + +class SubPartIdColumnSpec(exportcsv.ColumnSpec): + name = "Subpart Id" + + def extract_value(self, event_path): + subpart = self.get_subpart(event_path) + return None if subpart is None else subpart.id + + +class ModuleIdColumnSpec(exportcsv.ColumnSpec): + name = "Module Id" + + def extract_value(self, event_path): + module = self.get_module(event_path) + return module.id + + +class SeriesIdColumnSpec(exportcsv.ColumnSpec): + name = "Series ID" + + def extract_value(self, event_path): + series = self.get_series(event_path) + return series.id + + +class EventIdColumnSpec(exportcsv.EventAttrColumnSpec): + name = "Event ID" + attr_name = "id" + + +class EventLecturerColumnSpec(exportcsv.EventMetadataColumnSpec): + name = "People" + metadata_path = "people" + + def extract_value(self, event): + value = super(EventLecturerColumnSpec, self).extract_value(event) + return None if value is None else "#".join(value) diff --git a/app/django/timetables/management/commands/grasshopper_export_structure.py b/app/django/timetables/management/commands/grasshopper_export_structure.py new file mode 100644 index 0000000..40e9ddc --- /dev/null +++ b/app/django/timetables/management/commands/grasshopper_export_structure.py @@ -0,0 +1,110 @@ +""" +Copy this file to: + app/django/timetables/management/commands/grasshopper_export_structure.py + +Change directory so your current working directory is: + app/django + +Execute: + python manage.py grasshopper_export_structure > external-tree.json + + +Export the triposes, parts and subjects to a tree. The tree follows the same structure as GrassHopper's +import tree. i.e., + +- Course + - Subject (optional) + - Part + +The parts will also contain the external_url data object +""" +import csv +import sys +import argparse +import json + +import pytz + +from collections import defaultdict + +from timetables.models import Thing, Subjects +from timetables.utils import manage_commands + + +class Command(manage_commands.ArgparseBaseCommand): + + def __init__(self): + super(Command, self).__init__() + + self.parser = argparse.ArgumentParser( + prog="grasshopper_export_structure", + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + + def handle(self, args): + # Get the tree + tree = self.get_tree() + + # Print the tree + print json.dumps(tree, indent=4) + + def get_tree(self): + + root = { + 'id': 0, + 'name': 'Timetable', + 'type': 'root', + 'nodes': {} + } + + for item in Subjects.all_subjects(): + tripos = item.get_tripos() + part = item.get_part() + subject = item.get_most_significant_thing() + + + partData = None + if part.data: + partData = json.loads(part.data) + if partData['external_website_url']: + partData['external'] = partData['external_website_url'] + del partData['external_website_url'] + + + # Add the tripos/course into the tree + if tripos.id not in root['nodes']: + root['nodes'][tripos.id] = { + 'id': tripos.id, + 'name': tripos.fullname, + 'type': 'course', + 'nodes': {} + } + + # If we're dealing with a subject, add it in + if subject.id != part.id: + if subject.id not in root['nodes'][tripos.id]['nodes']: + root['nodes'][tripos.id]['nodes'][subject.id] = { + 'id': subject.id, + 'name': subject.fullname, + 'type': 'subject', + 'nodes': {} + } + + root['nodes'][tripos.id]['nodes'][subject.id]['nodes'][part.id] = { + 'id': part.id, + 'name': part.fullname, + 'data': partData, + 'type': 'part', + 'nodes': {} + } + else: + root['nodes'][tripos.id]['nodes'][part.id] = { + 'id': part.id, + 'name': part.fullname, + 'data': partData, + 'type': 'part', + 'nodes': {} + } + + return root diff --git a/app/django/timetables/management/commands/rollover_testcases.py b/app/django/timetables/management/commands/rollover_testcases.py new file mode 100644 index 0000000..8af22e7 --- /dev/null +++ b/app/django/timetables/management/commands/rollover_testcases.py @@ -0,0 +1,115 @@ +""" +Generate testcases to test event rollover from one academic year to another. +""" +from __future__ import unicode_literals + +import sys +import json +import argparse +import itertools +from datetime import time, datetime +from collections import OrderedDict + +import pytz + +from timetables.utils import manage_commands +from timetables.utils.academicyear import (TERM_MICHAELMAS, TERM_LENT, + TERM_EASTER, TERM_STARTS) +from timetables.utils.datetimes import termweek_to_date, DAYS_REVERSE + + +class Command(manage_commands.ArgparseBaseCommand): + + def __init__(self): + super(Command, self).__init__() + + self.parser = argparse.ArgumentParser( + prog="rollover_testcases", + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + + self.parser.add_argument("years", metavar="YEAR", nargs="+", + type=get_academic_year) + self.parser.add_argument("--roll-timezone", type=get_timezone, + default="Europe/London", dest="roll_tz") + self.parser.add_argument("--report-timezone", type=get_timezone, + default="UTC", dest="report_tz") + + def handle(self, args): + testcases = self.get_testcases(args.years, args.roll_tz, + args.report_tz) + + output = OrderedDict([ + ("roll_zone", args.roll_tz.zone), + ("years", args.years), + ("testcases", list(testcases)) + ]) + + json.dump(output, sys.stdout, indent=4) + + def get_testcases(self, years, roll_tz, report_tz): + assert len(years) > 0 + for term, week, day, hour in self.get_term_instants(): + minute = 0 + + testcase = OrderedDict([ + ("academic", OrderedDict([ + ("term", term), + ("week", week), + ("day", day), + ("hour", hour), + ("minute", minute) + ])) + ]) + testcase.update( + ("{:d}".format(year), + self.get_timestamp(year, term, week, day, hour, minute, + roll_tz).astimezone(report_tz).isoformat()) + for year in years + ) + + yield testcase + + def get_timestamp(self, year, term, week, day, hour, minute, timezone): + """ + Get the instant represented by a time, day and week in an academic term + as an aware datetime in the specified timezone. + """ + date = termweek_to_date(year, term, week, day) + return timezone.localize(datetime.combine(date, time(hour, minute))) + + def get_term_instants(self): + """ + Generate the timestamps to include as test cases. + + We produce one for each day of term (weeks 1-8 inclusive), plus 2 weeks + either side, so -1 to 10 inclusive. + """ + terms = [TERM_MICHAELMAS, TERM_LENT, TERM_EASTER] + weeks = range(-1, 11) + # Days from Thurs, Fri ... Wed + days = [DAYS_REVERSE[(n + 3) % 7] for n in range(0, 7)] + hours = [0, 5, 10, 12, 15, 20] + + return itertools.product(terms, weeks, days, hours) + + +def get_academic_year(year): + try: + year = int(year) + except ValueError: + raise argparse.ArgumentTypeError("Invalid year: {}".format(year)) + + if year not in TERM_STARTS: + raise argparse.ArgumentTypeError("No term dates available for year: {}" + .format(year)) + return year + + +def get_timezone(zone_name): + try: + return pytz.timezone(zone_name) + except pytz.UnknownTimeZoneError: + raise argparse.ArgumentTypeError( + "Unknown timezone: {0}".format(zone_name)) diff --git a/app/django/timetables/utils/traversal.py b/app/django/timetables/utils/traversal.py index 1f44e4b..fd309a2 100644 --- a/app/django/timetables/utils/traversal.py +++ b/app/django/timetables/utils/traversal.py @@ -26,8 +26,11 @@ def validate_obj(self): pass def step_up(self): + return self._step(self.get_parent()) + + def _step(self, parent): try: - traverser, parent = self.get_parent() + traverser, parent = parent return traverser(parent) except ValidationException as e: raise InvalidStructureException( @@ -80,20 +83,30 @@ def validate_obj(self): "Expected an EventSource instance", self.obj) def get_parent(self): + return next(self.get_parents()) + + def walk_parents(self): + """ + Enumerate all traversers for all parent modules of the current series. + """ + for parent in self.get_parents(): + yield self._step(parent) + + def get_parents(self): series = self.obj - tags = series.eventsourcetag_set.all() + tags = list(series.eventsourcetag_set.filter(annotation="home")) - try: - tag = next(tag for tag in tags if tag.annotation == "home") - module = tag.thing - except StopIteration: - raise ValidationException( + if(len(tags) == 0): + raise InvalidStructureException( "Orphaned series with no module encountered", series.pk) - if module.type != "module": - raise ValidationException( - "Series attached to non-module thing", series.pk) - return (ModuleTraverser, module) + for tag in tags: + module = tag.thing + + if module.type != "module": + raise InvalidStructureException( + "Series attached to non-module thing", series.pk) + yield (ModuleTraverser, module) class ModuleTraverser(ThingTraverserMixin, Traverser): diff --git a/fixtures/representative/README.md b/fixtures/representative/README.md new file mode 100644 index 0000000..a0b7a2b --- /dev/null +++ b/fixtures/representative/README.md @@ -0,0 +1,14 @@ +This directory contains a small dataset intended to be representative of all +of the types of data encountered in Timetable. + +The ``django-fixture.json`` file was created using: + + $ dj dumpdata --indent=4 --format=json -e timetables.ThingLock timetables + +``gh-data.csv`` was created by loading ``django-fixture.json`` into a clean Timetable install, then running: + + $ dj grasshopper_export_data + +``gh-tree.json`` was created by running [``etc/scripts/timetable/old-stack-import/generate-tree.js``](https://github.com/fronteerio/grasshopper/blob/bd5741355509af84d2b374172579b17d83e72ac5/etc/scripts/timetable/old-stack-import/generate-tree.js) from the [grasshopper](https://github.com/fronteerio/grasshopper) repo on ``gh-data.csv``. + +The data in all 3 files has been manually verified to be mutually consistent. diff --git a/fixtures/representative/django-fixture.json b/fixtures/representative/django-fixture.json new file mode 100644 index 0000000..5f3ef9f --- /dev/null +++ b/fixtures/representative/django-fixture.json @@ -0,0 +1,1086 @@ +[ +{ + "pk": 1, + "model": "timetables.thing", + "fields": { + "name": "tripos", + "parent": null, + "data": "", + "pathid": "p4pmLBPvFQjdJGPWzPW-yahYfT0=", + "fullpath": "tripos", + "type": "", + "fullname": "All Triposes" + } +}, +{ + "pk": 2, + "model": "timetables.thing", + "fields": { + "name": "user", + "parent": null, + "data": "", + "pathid": "Et6pb-wgWTVmq3VpLJlJWWgzrck=", + "fullpath": "user", + "type": "", + "fullname": "All Users" + } +}, +{ + "pk": 3, + "model": "timetables.thing", + "fields": { + "name": "hal", + "parent": 2, + "data": "{\"salt\": \"YYmFEIPopckMevmjcJVGkEVf274=\"}", + "pathid": "6cNw3dH9KHajMrM--6Cu-BikfQg=", + "fullpath": "user/hal", + "type": "user", + "fullname": "A Users Calendar" + } +}, +{ + "pk": 4, + "model": "timetables.thing", + "fields": { + "name": "simple-tripos", + "parent": 1, + "data": "", + "pathid": "MlUWOuWXW7qRoJnpBQjSAx5pE9g=", + "fullpath": "tripos/simple-tripos", + "type": "tripos", + "fullname": "Simple Tripos" + } +}, +{ + "pk": 5, + "model": "timetables.thing", + "fields": { + "name": "I", + "parent": 4, + "data": "", + "pathid": "sv4CGCwxit9dVfnCk1n15Zuxrm8=", + "fullpath": "tripos/simple-tripos/I", + "type": "part", + "fullname": "Part I" + } +}, +{ + "pk": 6, + "model": "timetables.thing", + "fields": { + "name": "II", + "parent": 4, + "data": "", + "pathid": "TlMuU-CJWtjv_1ZWoDYvx134yJI=", + "fullpath": "tripos/simple-tripos/II", + "type": "part", + "fullname": "Part II" + } +}, +{ + "pk": 7, + "model": "timetables.thing", + "fields": { + "name": "nested-subject", + "parent": 1, + "data": "", + "pathid": "bUM7__pG2izMa6ey6dBcYfcYxdg=", + "fullpath": "tripos/nested-subject", + "type": "tripos", + "fullname": "Nested Subject" + } +}, +{ + "pk": 8, + "model": "timetables.thing", + "fields": { + "name": "I", + "parent": 7, + "data": "", + "pathid": "zpUrgf6k6I_VYLIsDXWSBoHFnzE=", + "fullpath": "tripos/nested-subject/I", + "type": "part", + "fullname": "Part I" + } +}, +{ + "pk": 9, + "model": "timetables.thing", + "fields": { + "name": "II", + "parent": 7, + "data": "", + "pathid": "IxvwEE76HyEfCGFA0ymMCWgZbh4=", + "fullpath": "tripos/nested-subject/II", + "type": "part", + "fullname": "Part II" + } +}, +{ + "pk": 10, + "model": "timetables.thing", + "fields": { + "name": "subj-a", + "parent": 8, + "data": "", + "pathid": "JvLOzsX_dgeWcR51JBSHdVkvMBA=", + "fullpath": "tripos/nested-subject/I/subj-a", + "type": "subject", + "fullname": "Subject A" + } +}, +{ + "pk": 11, + "model": "timetables.thing", + "fields": { + "name": "subj-b", + "parent": 8, + "data": "", + "pathid": "O6H16VH52s5uNh3OraL9egTCWr4=", + "fullpath": "tripos/nested-subject/I/subj-b", + "type": "subject", + "fullname": "Subject B" + } +}, +{ + "pk": 12, + "model": "timetables.thing", + "fields": { + "name": "subj-a", + "parent": 9, + "data": "", + "pathid": "tyUGbz2hOdCV5EkZvPgvuU8tFjE=", + "fullpath": "tripos/nested-subject/II/subj-a", + "type": "subject", + "fullname": "Subject A" + } +}, +{ + "pk": 13, + "model": "timetables.thing", + "fields": { + "name": "module_a", + "parent": 5, + "data": "", + "pathid": "ssnoorNX1YmiSWNEAn0N9vEMNzc=", + "fullpath": "tripos/simple-tripos/I/module_a", + "type": "module", + "fullname": "Module A" + } +}, +{ + "pk": 14, + "model": "timetables.thing", + "fields": { + "name": "module_b", + "parent": 6, + "data": "", + "pathid": "LE0DWn9RbW8L2fy54MWOnPw4sDw=", + "fullpath": "tripos/simple-tripos/II/module_b", + "type": "module", + "fullname": "Module B" + } +}, +{ + "pk": 15, + "model": "timetables.thing", + "fields": { + "name": "module_c", + "parent": 6, + "data": "", + "pathid": "mw9hI_aHX8jxVe17DqmF8hDJMLs=", + "fullpath": "tripos/simple-tripos/II/module_c", + "type": "module", + "fullname": "Module C" + } +}, +{ + "pk": 16, + "model": "timetables.thing", + "fields": { + "name": "module_d", + "parent": 10, + "data": "", + "pathid": "ZiJjJQ5gMD-6qSvcI3yXwrY-lYc=", + "fullpath": "tripos/nested-subject/I/subj-a/module_d", + "type": "module", + "fullname": "Module D" + } +}, +{ + "pk": 17, + "model": "timetables.thing", + "fields": { + "name": "module_e", + "parent": 11, + "data": "", + "pathid": "E43to0nxSw6uL4TKan5rfvAftlA=", + "fullpath": "tripos/nested-subject/I/subj-b/module_e", + "type": "module", + "fullname": "Module E" + } +}, +{ + "pk": 18, + "model": "timetables.thing", + "fields": { + "name": "module_f", + "parent": 11, + "data": "", + "pathid": "I-z48bz1pxWVv_2SwS_RobnOcew=", + "fullpath": "tripos/nested-subject/I/subj-b/module_f", + "type": "module", + "fullname": "Module F" + } +}, +{ + "pk": 19, + "model": "timetables.thing", + "fields": { + "name": "module_g", + "parent": 12, + "data": "", + "pathid": "JhM27mjwSqCwkU335K5hyNXS_2Y=", + "fullpath": "tripos/nested-subject/II/subj-a/module_g", + "type": "module", + "fullname": "Module G" + } +}, +{ + "pk": 20, + "model": "timetables.thing", + "fields": { + "name": "module_h", + "parent": 12, + "data": "", + "pathid": "untJEcEfOUYMAXVs7OWpyTvlSFU=", + "fullpath": "tripos/nested-subject/II/subj-a/module_h", + "type": "module", + "fullname": "Module H" + } +}, +{ + "pk": 21, + "model": "timetables.thing", + "fields": { + "name": "common", + "parent": 10, + "data": "", + "pathid": "wlYI2aRxNbYDwSyANMuAiOyADyQ=", + "fullpath": "tripos/nested-subject/I/subj-a/common", + "type": "module", + "fullname": "Common" + } +}, +{ + "pk": 22, + "model": "timetables.thing", + "fields": { + "name": "common", + "parent": 12, + "data": "", + "pathid": "xnS8_sJZB1cxp0yrcaKIK_ihGdw=", + "fullpath": "tripos/nested-subject/II/subj-a/common", + "type": "module", + "fullname": "Common" + } +}, +{ + "pk": 23, + "model": "timetables.thing", + "fields": { + "name": "External", + "parent": 4, + "data": "{\r\n \"external_website_url\": \"https://www.example.org/\"\r\n}", + "pathid": "h01nE8b2A-5I0kcrMjrMagMDZOo=", + "fullpath": "tripos/simple-tripos/External", + "type": "part", + "fullname": "External Part" + } +}, +{ + "pk": 1, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series A", + "sourcefile": "", + "current": true, + "master": 1, + "data": "{\"location\": \"Room B\", \"people\": [\"Dr A\", \"Mr B and Ms C\"], \"datePattern\": \"Mi1-2 Th 9\"}", + "versionstamp": "2015-05-22T20:05:37.722Z" + } +}, +{ + "pk": 2, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series B", + "sourcefile": "", + "current": true, + "master": 2, + "data": "{\"datePattern\": \"Mi4 M 12\", \"location\": \"Room B\", \"people\": [\"Mr ABC\"]}", + "versionstamp": "2015-05-22T20:16:57.322Z" + } +}, +{ + "pk": 3, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series C", + "sourcefile": "", + "current": true, + "master": 3, + "data": "{\"datePattern\": \"Le4 Tu 2:25-3:46\", \"location\": \"Room C\", \"people\": [\"abc12\", \"def34\"]}", + "versionstamp": "2015-05-22T20:21:01.215Z" + } +}, +{ + "pk": 4, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series D 1", + "sourcefile": "", + "current": true, + "master": 4, + "data": "{\"location\": \"Room D\", \"people\": [\"Prof D\"], \"datePattern\": \"Mi1 Le1 Ea1 F 12\"}", + "versionstamp": "2015-05-22T20:30:20.208Z" + } +}, +{ + "pk": 5, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series D2", + "sourcefile": "", + "current": true, + "master": 5, + "data": "{\"datePattern\": \"Mi-1,0,9-10 Th 9\", \"location\": \"Room D\", \"people\": [\"Mr Pink\"]}", + "versionstamp": "2015-05-22T20:38:01.420Z" + } +}, +{ + "pk": 6, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series E", + "sourcefile": "", + "current": true, + "master": 6, + "data": "{\"datePattern\": \"Mi1 Tu 2\", \"location\": \"Room E\", \"people\": [\"Ms E\"]}", + "versionstamp": "2015-05-22T20:42:35.392Z" + } +}, +{ + "pk": 7, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series F", + "sourcefile": "", + "current": true, + "master": 7, + "data": "{\"datePattern\": \"Mi5 F 8\", \"location\": \"Room F\", \"people\": [\"Mr F\"]}", + "versionstamp": "2015-05-22T20:44:11.531Z" + } +}, +{ + "pk": 8, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series G", + "sourcefile": "", + "current": true, + "master": 8, + "data": "{\"datePattern\": \"Le4 M 1\", \"location\": \"Room G\", \"people\": [\"Gee Gson\"]}", + "versionstamp": "2015-05-22T20:49:19.770Z" + } +}, +{ + "pk": 9, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series H", + "sourcefile": "", + "current": true, + "master": 9, + "data": "{\"datePattern\": \"Mi4 Th 5\", \"location\": \"Room H\", \"people\": [\"Dr H\"]}", + "versionstamp": "2015-05-22T20:52:05.040Z" + } +}, +{ + "pk": 10, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Shared Subject A Series", + "sourcefile": "", + "current": true, + "master": 10, + "data": "{\"datePattern\": \"Mi1 Th 9\", \"location\": \"A room\", \"people\": [\"Mr Blue\"]}", + "versionstamp": "2015-05-22T21:10:30.195Z" + } +}, +{ + "pk": 1, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T20:08:02.290Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-09T08:00:00Z", + "master": null, + "location": "Room A", + "uid": "d6560f87-3c7e-49ff-8706-16ba321cb490", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 2, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T20:08:18.570Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-09T08:00:00Z", + "master": 1, + "location": "Room A", + "uid": "d6560f87-3c7e-49ff-8706-16ba321cb490", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 3, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-16T09:00:00Z", + "versionstamp": "2015-05-22T20:08:18.587Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-16T08:00:00Z", + "master": null, + "location": "Room A", + "uid": "90bcdace-b09c-4860-8e86-1a2527931375", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 4, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T20:15:33.343Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-09T08:00:00Z", + "master": 1, + "location": "Room A", + "uid": "d6560f87-3c7e-49ff-8706-16ba321cb490", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 5, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-16T09:00:00Z", + "versionstamp": "2015-05-22T20:15:33.367Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-16T08:00:00Z", + "master": 3, + "location": "Room B", + "uid": "90bcdace-b09c-4860-8e86-1a2527931375", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\", \"Mr B and Ms C\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 6, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T20:15:45.222Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-09T08:00:00Z", + "master": 1, + "location": "Room A", + "uid": "d6560f87-3c7e-49ff-8706-16ba321cb490", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 7, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-16T09:00:00Z", + "versionstamp": "2015-05-22T20:15:45.241Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-16T08:00:00Z", + "master": 3, + "location": "Room B", + "uid": "90bcdace-b09c-4860-8e86-1a2527931375", + "data": "{\"type\": \"field trip\", \"people\": [\"Dr A\", \"Mr B and Ms C\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 8, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-11-03T13:00:00Z", + "versionstamp": "2015-05-22T20:20:42.584Z", + "title": "Thing B", + "source": 2, + "endtz": "Europe/London", + "current": true, + "start": "2014-11-03T12:00:00Z", + "master": null, + "location": "Room B", + "uid": "ef6ad702-7a52-45cd-8b18-09ffcff660b9", + "data": "{\"type\": \"practical\", \"people\": [\"Mr ABC\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 9, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-02-10T15:46:00Z", + "versionstamp": "2015-05-22T20:24:11.291Z", + "title": "Thing C", + "source": 3, + "endtz": "Europe/London", + "current": true, + "start": "2015-02-10T14:25:00Z", + "master": null, + "location": "Room C", + "uid": "17e8f1f6-3813-4066-911a-6e6ae4888add", + "data": "{\"type\": \"workshop\", \"people\": [\"abc12\", \"def34\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 10, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-10T12:00:00Z", + "versionstamp": "2015-05-22T20:34:01.717Z", + "title": "D1", + "source": 4, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-10T11:00:00Z", + "master": null, + "location": "Room D", + "uid": "efd977cd-eb72-42e5-8516-8f6883620f28", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 11, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-10T12:00:00Z", + "versionstamp": "2015-05-22T20:34:17.279Z", + "title": "D1", + "source": 4, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-10T11:00:00Z", + "master": 10, + "location": "Room D", + "uid": "efd977cd-eb72-42e5-8516-8f6883620f28", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 12, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-01-16T13:00:00Z", + "versionstamp": "2015-05-22T20:34:17.295Z", + "title": "D2", + "source": 4, + "endtz": "Europe/London", + "current": false, + "start": "2015-01-16T12:00:00Z", + "master": null, + "location": "Room D", + "uid": "77aadf4b-2a54-48f0-9d3b-3818471c95cf", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 13, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-10T12:00:00Z", + "versionstamp": "2015-05-22T20:34:31.606Z", + "title": "D1", + "source": 4, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-10T11:00:00Z", + "master": 10, + "location": "Room D", + "uid": "efd977cd-eb72-42e5-8516-8f6883620f28", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 14, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-01-16T13:00:00Z", + "versionstamp": "2015-05-22T20:34:31.625Z", + "title": "D2", + "source": 4, + "endtz": "Europe/London", + "current": true, + "start": "2015-01-16T12:00:00Z", + "master": 12, + "location": "Room D", + "uid": "77aadf4b-2a54-48f0-9d3b-3818471c95cf", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 15, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-04-24T12:00:00Z", + "versionstamp": "2015-05-22T20:34:31.640Z", + "title": "D3", + "source": 4, + "endtz": "Europe/London", + "current": true, + "start": "2015-04-24T11:00:00Z", + "master": null, + "location": "Room D", + "uid": "634053a1-a29c-4ecc-aa42-61c836208857", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 16, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-09-25T09:00:00Z", + "versionstamp": "2015-05-22T20:40:04.192Z", + "title": "D2 Thing", + "source": 5, + "endtz": "Europe/London", + "current": true, + "start": "2014-09-25T08:00:00Z", + "master": null, + "location": "Room D", + "uid": "2ee5f91e-9d3d-46fd-8bf6-f54a5bf15498", + "data": "{\"type\": \"presentation\", \"people\": [\"Mr Pink\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 17, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-02T09:00:00Z", + "versionstamp": "2015-05-22T20:40:04.211Z", + "title": "D2 Thing", + "source": 5, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-02T08:00:00Z", + "master": null, + "location": "Room D", + "uid": "16b69975-be65-41d2-a12c-6c10fd072e27", + "data": "{\"type\": \"presentation\", \"people\": [\"Mr Pink\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 18, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-12-04T10:00:00Z", + "versionstamp": "2015-05-22T20:40:04.228Z", + "title": "D2 Thing", + "source": 5, + "endtz": "Europe/London", + "current": true, + "start": "2014-12-04T09:00:00Z", + "master": null, + "location": "Room D", + "uid": "2f3761e1-1205-4262-85ae-2e8a22044bc4", + "data": "{\"type\": \"presentation\", \"people\": [\"Mr Pink\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 19, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-12-11T10:00:00Z", + "versionstamp": "2015-05-22T20:40:04.243Z", + "title": "D2 Thing", + "source": 5, + "endtz": "Europe/London", + "current": true, + "start": "2014-12-11T09:00:00Z", + "master": null, + "location": "Room D", + "uid": "28874f98-8e5b-40a4-a64a-699010af7f70", + "data": "{\"type\": \"presentation\", \"people\": [\"Mr Pink\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 20, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-14T14:00:00Z", + "versionstamp": "2015-05-22T20:43:53.282Z", + "title": "Event E", + "source": 6, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-14T13:00:00Z", + "master": null, + "location": "Room E", + "uid": "13764e88-afb0-4407-89fb-9924c9d108a9", + "data": "{\"type\": \"class\", \"people\": [\"Ms E\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 21, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-11-07T09:00:00Z", + "versionstamp": "2015-05-22T20:48:50.190Z", + "title": "Event F", + "source": 7, + "endtz": "Europe/London", + "current": true, + "start": "2014-11-07T08:00:00Z", + "master": null, + "location": "Room F", + "uid": "75367174-0791-4132-89bb-541e19d64ac4", + "data": "{\"type\": \"seminar\", \"people\": [\"Mr F\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 22, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-02-09T14:00:00Z", + "versionstamp": "2015-05-22T20:51:09.211Z", + "title": "Event G", + "source": 8, + "endtz": "Europe/London", + "current": true, + "start": "2015-02-09T13:00:00Z", + "master": null, + "location": "Room G", + "uid": "288e88be-9fa0-46e1-bb94-cbd8ce39dc3d", + "data": "{\"type\": \"seminar\", \"people\": [\"Gee Gson\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 23, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-30T18:00:00Z", + "versionstamp": "2015-05-22T20:52:59.525Z", + "title": "Event H", + "source": 9, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-30T17:00:00Z", + "master": null, + "location": "Room H", + "uid": "9ae81002-5484-47ee-a54c-fffe5cf5b8c1", + "data": "{\"type\": \"\", \"people\": [\"Dr H\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 24, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T21:13:41.890Z", + "title": "Intro to Subject A", + "source": 10, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-09T08:00:00Z", + "master": null, + "location": "A room", + "uid": "6b4fd2f0-8983-4ca8-ab19-ff7fd1301f37", + "data": "{\"type\": \"workshop\", \"people\": [\"Mr Blue\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 1, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 13, + "eventsource": 1, + "annotation": "home" + } +}, +{ + "pk": 2, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 14, + "eventsource": 2, + "annotation": "home" + } +}, +{ + "pk": 3, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 15, + "eventsource": 3, + "annotation": "home" + } +}, +{ + "pk": 4, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 16, + "eventsource": 4, + "annotation": "home" + } +}, +{ + "pk": 5, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 16, + "eventsource": 5, + "annotation": "home" + } +}, +{ + "pk": 6, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 17, + "eventsource": 6, + "annotation": "home" + } +}, +{ + "pk": 7, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 18, + "eventsource": 7, + "annotation": "home" + } +}, +{ + "pk": 8, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 19, + "eventsource": 8, + "annotation": "home" + } +}, +{ + "pk": 9, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 20, + "eventsource": 9, + "annotation": "home" + } +}, +{ + "pk": 10, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 3, + "eventsource": 4, + "annotation": null + } +}, +{ + "pk": 11, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 3, + "eventsource": 8, + "annotation": null + } +}, +{ + "pk": 12, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 3, + "eventsource": 9, + "annotation": null + } +}, +{ + "pk": 13, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 21, + "eventsource": 10, + "annotation": "home" + } +}, +{ + "pk": 14, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 22, + "eventsource": 10, + "annotation": "home" + } +}, +{ + "pk": 15, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 3, + "eventsource": 10, + "annotation": null + } +}, +{ + "pk": 1, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 6, + "annotation": "admin" + } +}, +{ + "pk": 2, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 5, + "annotation": "admin" + } +}, +{ + "pk": 3, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 12, + "annotation": "admin" + } +}, +{ + "pk": 4, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 11, + "annotation": "admin" + } +}, +{ + "pk": 5, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 10, + "annotation": "admin" + } +}, +{ + "pk": 6, + "model": "timetables.thingtag", + "fields": { + "thing": 23, + "targetthing": 23, + "annotation": "disabled" + } +} +] diff --git a/fixtures/representative/gh-data.csv b/fixtures/representative/gh-data.csv new file mode 100644 index 0000000..1031f48 --- /dev/null +++ b/fixtures/representative/gh-data.csv @@ -0,0 +1,18 @@ +"Tripos Id","Tripos Name","Part Id","Part Name","Subpart Id","Subpart Name","Module Id","Module Name","Series ID","Series Name","Event ID","Title","Type","Start","End","Location","People" +"4","Simple Tripos","5","Part I","","","13","Module A","1","Series A","6","Blah A","journal club","2014-10-09T09:00:00+01:00","2014-10-09T10:00:00+01:00","Room A","Dr A" +"4","Simple Tripos","5","Part I","","","13","Module A","1","Series A","7","Blah A","field trip","2014-10-16T09:00:00+01:00","2014-10-16T10:00:00+01:00","Room B","Dr A#Mr B and Ms C" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","5","Series D2","18","D2 Thing","presentation","2014-12-04T09:00:00+00:00","2014-12-04T10:00:00+00:00","Room D","Mr Pink" +"4","Simple Tripos","6","Part II","","","14","Module B","2","Series B","8","Thing B","practical","2014-11-03T12:00:00+00:00","2014-11-03T13:00:00+00:00","Room B","Mr ABC" +"4","Simple Tripos","6","Part II","","","15","Module C","3","Series C","9","Thing C","workshop","2015-02-10T14:25:00+00:00","2015-02-10T15:46:00+00:00","Room C","abc12#def34" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","4","Series D 1","13","D1","lecture","2014-10-10T12:00:00+01:00","2014-10-10T13:00:00+01:00","Room D","Prof D" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","4","Series D 1","14","D2","lecture","2015-01-16T12:00:00+00:00","2015-01-16T13:00:00+00:00","Room D","Prof D" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","5","Series D2","19","D2 Thing","presentation","2014-12-11T09:00:00+00:00","2014-12-11T10:00:00+00:00","Room D","Mr Pink" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","4","Series D 1","15","D3","lecture","2015-04-24T12:00:00+01:00","2015-04-24T13:00:00+01:00","Room D","Prof D" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","5","Series D2","16","D2 Thing","presentation","2014-09-25T09:00:00+01:00","2014-09-25T10:00:00+01:00","Room D","Mr Pink" +"7","Nested Subject","9","Part II","12","Subject A","20","Module H","9","Series H","23","Event H","","2014-10-30T17:00:00+00:00","2014-10-30T18:00:00+00:00","Room H","Dr H" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","5","Series D2","17","D2 Thing","presentation","2014-10-02T09:00:00+01:00","2014-10-02T10:00:00+01:00","Room D","Mr Pink" +"7","Nested Subject","8","Part I","11","Subject B","17","Module E","6","Series E","20","Event E","class","2014-10-14T14:00:00+01:00","2014-10-14T15:00:00+01:00","Room E","Ms E" +"7","Nested Subject","8","Part I","10","Subject A","21","Common","10","Shared Subject A Series","24","Intro to Subject A","workshop","2014-10-09T09:00:00+01:00","2014-10-09T10:00:00+01:00","A room","Mr Blue" +"7","Nested Subject","9","Part II","12","Subject A","22","Common","10","Shared Subject A Series","24","Intro to Subject A","workshop","2014-10-09T09:00:00+01:00","2014-10-09T10:00:00+01:00","A room","Mr Blue" +"7","Nested Subject","8","Part I","11","Subject B","18","Module F","7","Series F","21","Event F","seminar","2014-11-07T08:00:00+00:00","2014-11-07T09:00:00+00:00","Room F","Mr F" +"7","Nested Subject","9","Part II","12","Subject A","19","Module G","8","Series G","22","Event G","seminar","2015-02-09T13:00:00+00:00","2015-02-09T14:00:00+00:00","Room G","Gee Gson" diff --git a/fixtures/representative/gh-tree.json b/fixtures/representative/gh-tree.json new file mode 100644 index 0000000..cd57a29 --- /dev/null +++ b/fixtures/representative/gh-tree.json @@ -0,0 +1,423 @@ +{ + "name": "Timetable", + "type": "root", + "nodes": { + "4": { + "id": "4", + "name": "Simple Tripos", + "type": "course", + "nodes": { + "5-1": { + "id": "5-1", + "name": "Part I", + "type": "part", + "nodes": { + "13": { + "id": "13", + "name": "Module A", + "type": "module", + "nodes": { + "1": { + "id": "1", + "name": "Series A", + "type": "series", + "nodes": { + "6": { + "id": "6", + "name": "Blah A", + "type": "event", + "event-type": "journal club", + "start": "2014-10-09T09:00:00+01:00", + "end": "2014-10-09T10:00:00+01:00", + "location": "Room A", + "people": [ + "Dr A" + ] + }, + "7": { + "id": "7", + "name": "Blah A", + "type": "event", + "event-type": "field trip", + "start": "2014-10-16T09:00:00+01:00", + "end": "2014-10-16T10:00:00+01:00", + "location": "Room B", + "people": [ + "Dr A", + "Mr B", + "Ms C" + ] + } + } + } + } + } + } + }, + "6-3": { + "id": "6-3", + "name": "Part II", + "type": "part", + "nodes": { + "14": { + "id": "14", + "name": "Module B", + "type": "module", + "nodes": { + "2": { + "id": "2", + "name": "Series B", + "type": "series", + "nodes": { + "8": { + "id": "8", + "name": "Thing B", + "type": "event", + "event-type": "practical", + "start": "2014-11-03T12:00:00+00:00", + "end": "2014-11-03T13:00:00+00:00", + "location": "Room B", + "people": [ + "Mr ABC" + ] + } + } + } + } + }, + "15": { + "id": "15", + "name": "Module C", + "type": "module", + "nodes": { + "3": { + "id": "3", + "name": "Series C", + "type": "series", + "nodes": { + "9": { + "id": "9", + "name": "Thing C", + "type": "event", + "event-type": "workshop", + "start": "2015-02-10T14:25:00+00:00", + "end": "2015-02-10T15:46:00+00:00", + "location": "Room C", + "people": [ + "abc12", + "def34" + ] + } + } + } + } + } + } + } + } + }, + "7": { + "id": "7", + "name": "Nested Subject", + "type": "course", + "nodes": { + "Subject A": { + "id": "Subject A", + "type": "subject", + "name": "Subject A", + "nodes": { + "8-2": { + "id": "8-2", + "name": "Part I", + "type": "part", + "nodes": { + "16": { + "id": "16", + "name": "Module D", + "type": "module", + "nodes": { + "4": { + "id": "4", + "name": "Series D 1", + "type": "series", + "nodes": { + "13": { + "id": "13", + "name": "D1", + "type": "event", + "event-type": "lecture", + "start": "2014-10-10T12:00:00+01:00", + "end": "2014-10-10T13:00:00+01:00", + "location": "Room D", + "people": [ + "Prof D" + ] + }, + "14": { + "id": "14", + "name": "D2", + "type": "event", + "event-type": "lecture", + "start": "2015-01-16T12:00:00+00:00", + "end": "2015-01-16T13:00:00+00:00", + "location": "Room D", + "people": [ + "Prof D" + ] + }, + "15": { + "id": "15", + "name": "D3", + "type": "event", + "event-type": "lecture", + "start": "2015-04-24T12:00:00+01:00", + "end": "2015-04-24T13:00:00+01:00", + "location": "Room D", + "people": [ + "Prof D" + ] + } + } + }, + "5": { + "id": "5", + "name": "Series D2", + "type": "series", + "nodes": { + "16": { + "id": "16", + "name": "D2 Thing", + "type": "event", + "event-type": "presentation", + "start": "2014-09-25T09:00:00+01:00", + "end": "2014-09-25T10:00:00+01:00", + "location": "Room D", + "people": [ + "Mr Pink" + ] + }, + "17": { + "id": "17", + "name": "D2 Thing", + "type": "event", + "event-type": "presentation", + "start": "2014-10-02T09:00:00+01:00", + "end": "2014-10-02T10:00:00+01:00", + "location": "Room D", + "people": [ + "Mr Pink" + ] + }, + "18": { + "id": "18", + "name": "D2 Thing", + "type": "event", + "event-type": "presentation", + "start": "2014-12-04T09:00:00+00:00", + "end": "2014-12-04T10:00:00+00:00", + "location": "Room D", + "people": [ + "Mr Pink" + ] + }, + "19": { + "id": "19", + "name": "D2 Thing", + "type": "event", + "event-type": "presentation", + "start": "2014-12-11T09:00:00+00:00", + "end": "2014-12-11T10:00:00+00:00", + "location": "Room D", + "people": [ + "Mr Pink" + ] + } + } + } + } + }, + "21": { + "id": "21", + "name": "Common", + "type": "module", + "nodes": { + "10": { + "id": "10", + "name": "Shared Subject A Series", + "type": "series", + "nodes": { + "24": { + "id": "24", + "name": "Intro to Subject A", + "type": "event", + "event-type": "workshop", + "start": "2014-10-09T09:00:00+01:00", + "end": "2014-10-09T10:00:00+01:00", + "location": "A room", + "people": [ + "Mr Blue" + ] + } + } + } + } + } + } + }, + "9-4": { + "id": "9-4", + "name": "Part II", + "type": "part", + "nodes": { + "19": { + "id": "19", + "name": "Module G", + "type": "module", + "nodes": { + "8": { + "id": "8", + "name": "Series G", + "type": "series", + "nodes": { + "22": { + "id": "22", + "name": "Event G", + "type": "event", + "event-type": "seminar", + "start": "2015-02-09T13:00:00+00:00", + "end": "2015-02-09T14:00:00+00:00", + "location": "Room G", + "people": [ + "Gee Gson" + ] + } + } + } + } + }, + "20": { + "id": "20", + "name": "Module H", + "type": "module", + "nodes": { + "9": { + "id": "9", + "name": "Series H", + "type": "series", + "nodes": { + "23": { + "id": "23", + "name": "Event H", + "type": "event", + "event-type": "", + "start": "2014-10-30T17:00:00+00:00", + "end": "2014-10-30T18:00:00+00:00", + "location": "Room H", + "people": [ + "Dr H" + ] + } + } + } + } + }, + "22": { + "id": "22", + "name": "Common", + "type": "module", + "nodes": { + "10": { + "id": "10", + "name": "Shared Subject A Series", + "type": "series", + "nodes": { + "24": { + "id": "24", + "name": "Intro to Subject A", + "type": "event", + "event-type": "workshop", + "start": "2014-10-09T09:00:00+01:00", + "end": "2014-10-09T10:00:00+01:00", + "location": "A room", + "people": [ + "Mr Blue" + ] + } + } + } + } + } + } + } + } + }, + "Subject B": { + "id": "Subject B", + "type": "subject", + "name": "Subject B", + "nodes": { + "8-5": { + "id": "8-5", + "name": "Part I", + "type": "part", + "nodes": { + "17": { + "id": "17", + "name": "Module E", + "type": "module", + "nodes": { + "6": { + "id": "6", + "name": "Series E", + "type": "series", + "nodes": { + "20": { + "id": "20", + "name": "Event E", + "type": "event", + "event-type": "class", + "start": "2014-10-14T14:00:00+01:00", + "end": "2014-10-14T15:00:00+01:00", + "location": "Room E", + "people": [ + "Ms E" + ] + } + } + } + } + }, + "18": { + "id": "18", + "name": "Module F", + "type": "module", + "nodes": { + "7": { + "id": "7", + "name": "Series F", + "type": "series", + "nodes": { + "21": { + "id": "21", + "name": "Event F", + "type": "event", + "event-type": "seminar", + "start": "2014-11-07T08:00:00+00:00", + "end": "2014-11-07T09:00:00+00:00", + "location": "Room F", + "people": [ + "Mr F" + ] + } + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/fixtures/representative/structure-verification/django-fixture.json b/fixtures/representative/structure-verification/django-fixture.json new file mode 100644 index 0000000..f3206b3 --- /dev/null +++ b/fixtures/representative/structure-verification/django-fixture.json @@ -0,0 +1,1194 @@ +[ +{ + "pk": 1, + "model": "timetables.thing", + "fields": { + "name": "tripos", + "parent": null, + "data": "", + "pathid": "p4pmLBPvFQjdJGPWzPW-yahYfT0=", + "fullpath": "tripos", + "type": "", + "fullname": "All Triposes" + } +}, +{ + "pk": 2, + "model": "timetables.thing", + "fields": { + "name": "user", + "parent": null, + "data": "", + "pathid": "Et6pb-wgWTVmq3VpLJlJWWgzrck=", + "fullpath": "user", + "type": "", + "fullname": "All Users" + } +}, +{ + "pk": 3, + "model": "timetables.thing", + "fields": { + "name": "hal", + "parent": 2, + "data": "{\"salt\": \"YYmFEIPopckMevmjcJVGkEVf274=\"}", + "pathid": "6cNw3dH9KHajMrM--6Cu-BikfQg=", + "fullpath": "user/hal", + "type": "user", + "fullname": "A Users Calendar" + } +}, +{ + "pk": 4, + "model": "timetables.thing", + "fields": { + "name": "simple-tripos", + "parent": 1, + "data": "", + "pathid": "MlUWOuWXW7qRoJnpBQjSAx5pE9g=", + "fullpath": "tripos/simple-tripos", + "type": "tripos", + "fullname": "Simple Tripos" + } +}, +{ + "pk": 5, + "model": "timetables.thing", + "fields": { + "name": "I", + "parent": 4, + "data": "", + "pathid": "sv4CGCwxit9dVfnCk1n15Zuxrm8=", + "fullpath": "tripos/simple-tripos/I", + "type": "part", + "fullname": "Part I" + } +}, +{ + "pk": 6, + "model": "timetables.thing", + "fields": { + "name": "II", + "parent": 4, + "data": "", + "pathid": "TlMuU-CJWtjv_1ZWoDYvx134yJI=", + "fullpath": "tripos/simple-tripos/II", + "type": "part", + "fullname": "Part II" + } +}, +{ + "pk": 7, + "model": "timetables.thing", + "fields": { + "name": "nested-subject", + "parent": 1, + "data": "", + "pathid": "bUM7__pG2izMa6ey6dBcYfcYxdg=", + "fullpath": "tripos/nested-subject", + "type": "tripos", + "fullname": "Nested Subject" + } +}, +{ + "pk": 8, + "model": "timetables.thing", + "fields": { + "name": "I", + "parent": 7, + "data": "", + "pathid": "zpUrgf6k6I_VYLIsDXWSBoHFnzE=", + "fullpath": "tripos/nested-subject/I", + "type": "part", + "fullname": "Part I" + } +}, +{ + "pk": 9, + "model": "timetables.thing", + "fields": { + "name": "II", + "parent": 7, + "data": "", + "pathid": "IxvwEE76HyEfCGFA0ymMCWgZbh4=", + "fullpath": "tripos/nested-subject/II", + "type": "part", + "fullname": "Part II" + } +}, +{ + "pk": 10, + "model": "timetables.thing", + "fields": { + "name": "subj-a", + "parent": 8, + "data": "", + "pathid": "JvLOzsX_dgeWcR51JBSHdVkvMBA=", + "fullpath": "tripos/nested-subject/I/subj-a", + "type": "subject", + "fullname": "Subject A" + } +}, +{ + "pk": 11, + "model": "timetables.thing", + "fields": { + "name": "subj-b", + "parent": 8, + "data": "", + "pathid": "O6H16VH52s5uNh3OraL9egTCWr4=", + "fullpath": "tripos/nested-subject/I/subj-b", + "type": "subject", + "fullname": "Subject B" + } +}, +{ + "pk": 12, + "model": "timetables.thing", + "fields": { + "name": "subj-a", + "parent": 9, + "data": "", + "pathid": "tyUGbz2hOdCV5EkZvPgvuU8tFjE=", + "fullpath": "tripos/nested-subject/II/subj-a", + "type": "subject", + "fullname": "Subject A" + } +}, +{ + "pk": 13, + "model": "timetables.thing", + "fields": { + "name": "module_a", + "parent": 5, + "data": "", + "pathid": "ssnoorNX1YmiSWNEAn0N9vEMNzc=", + "fullpath": "tripos/simple-tripos/I/module_a", + "type": "module", + "fullname": "Module A" + } +}, +{ + "pk": 14, + "model": "timetables.thing", + "fields": { + "name": "module_b", + "parent": 6, + "data": "", + "pathid": "LE0DWn9RbW8L2fy54MWOnPw4sDw=", + "fullpath": "tripos/simple-tripos/II/module_b", + "type": "module", + "fullname": "Module B" + } +}, +{ + "pk": 15, + "model": "timetables.thing", + "fields": { + "name": "module_c", + "parent": 6, + "data": "", + "pathid": "mw9hI_aHX8jxVe17DqmF8hDJMLs=", + "fullpath": "tripos/simple-tripos/II/module_c", + "type": "module", + "fullname": "Module C" + } +}, +{ + "pk": 16, + "model": "timetables.thing", + "fields": { + "name": "module_d", + "parent": 10, + "data": "", + "pathid": "ZiJjJQ5gMD-6qSvcI3yXwrY-lYc=", + "fullpath": "tripos/nested-subject/I/subj-a/module_d", + "type": "module", + "fullname": "Module D" + } +}, +{ + "pk": 17, + "model": "timetables.thing", + "fields": { + "name": "module_e", + "parent": 11, + "data": "", + "pathid": "E43to0nxSw6uL4TKan5rfvAftlA=", + "fullpath": "tripos/nested-subject/I/subj-b/module_e", + "type": "module", + "fullname": "Module E" + } +}, +{ + "pk": 18, + "model": "timetables.thing", + "fields": { + "name": "module_f", + "parent": 11, + "data": "", + "pathid": "I-z48bz1pxWVv_2SwS_RobnOcew=", + "fullpath": "tripos/nested-subject/I/subj-b/module_f", + "type": "module", + "fullname": "Module F" + } +}, +{ + "pk": 19, + "model": "timetables.thing", + "fields": { + "name": "module_g", + "parent": 12, + "data": "", + "pathid": "JhM27mjwSqCwkU335K5hyNXS_2Y=", + "fullpath": "tripos/nested-subject/II/subj-a/module_g", + "type": "module", + "fullname": "Module G" + } +}, +{ + "pk": 20, + "model": "timetables.thing", + "fields": { + "name": "module_h", + "parent": 12, + "data": "", + "pathid": "untJEcEfOUYMAXVs7OWpyTvlSFU=", + "fullpath": "tripos/nested-subject/II/subj-a/module_h", + "type": "module", + "fullname": "Module H" + } +}, +{ + "pk": 21, + "model": "timetables.thing", + "fields": { + "name": "common", + "parent": 10, + "data": "", + "pathid": "wlYI2aRxNbYDwSyANMuAiOyADyQ=", + "fullpath": "tripos/nested-subject/I/subj-a/common", + "type": "module", + "fullname": "Common" + } +}, +{ + "pk": 22, + "model": "timetables.thing", + "fields": { + "name": "common", + "parent": 12, + "data": "", + "pathid": "xnS8_sJZB1cxp0yrcaKIK_ihGdw=", + "fullpath": "tripos/nested-subject/II/subj-a/common", + "type": "module", + "fullname": "Common" + } +}, +{ + "pk": 23, + "model": "timetables.thing", + "fields": { + "name": "External", + "parent": 4, + "data": "{\r\n \"external_website_url\": \"https://www.example.org/\"\r\n}", + "pathid": "h01nE8b2A-5I0kcrMjrMagMDZOo=", + "fullpath": "tripos/simple-tripos/External", + "type": "part", + "fullname": "External Part" + } +}, +{ + "pk": 24, + "model": "timetables.thing", + "fields": { + "name": "disabled_module", + "parent": 23, + "data": "", + "pathid": "GHyFEV_W17d7IrZaejM7BkB_3y8=", + "fullpath": "tripos/simple-tripos/External/disabled_module", + "type": "module", + "fullname": "Disabled Module" + } +}, +{ + "pk": 25, + "model": "timetables.thing", + "fields": { + "name": "external2", + "parent": 4, + "data": "{\r\n \"external_website_url\": \"https://www.example.org/2\"\r\n}", + "pathid": "-mKIjmOYdYVwsC-Q_2ZumKEmpuk=", + "fullpath": "tripos/simple-tripos/external2", + "type": "part", + "fullname": "External (no events with URL)" + } +}, +{ + "pk": 26, + "model": "timetables.thing", + "fields": { + "name": "external3", + "parent": 4, + "data": "", + "pathid": "wnWItYPDv6V5tEIWuD_3pczoarY=", + "fullpath": "tripos/simple-tripos/external3", + "type": "part", + "fullname": "External (no events no URL)" + } +}, +{ + "pk": 1, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series A", + "sourcefile": "", + "current": true, + "master": 1, + "data": "{\"location\": \"Room B\", \"people\": [\"Dr A\", \"Mr B and Ms C\"], \"datePattern\": \"Mi1-2 Th 9\"}", + "versionstamp": "2015-05-22T20:05:37.722Z" + } +}, +{ + "pk": 2, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series B", + "sourcefile": "", + "current": true, + "master": 2, + "data": "{\"datePattern\": \"Mi4 M 12\", \"location\": \"Room B\", \"people\": [\"Mr ABC\"]}", + "versionstamp": "2015-05-22T20:16:57.322Z" + } +}, +{ + "pk": 3, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series C", + "sourcefile": "", + "current": true, + "master": 3, + "data": "{\"datePattern\": \"Le4 Tu 2:25-3:46\", \"location\": \"Room C\", \"people\": [\"abc12\", \"def34\"]}", + "versionstamp": "2015-05-22T20:21:01.215Z" + } +}, +{ + "pk": 4, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series D 1", + "sourcefile": "", + "current": true, + "master": 4, + "data": "{\"location\": \"Room D\", \"people\": [\"Prof D\"], \"datePattern\": \"Mi1 Le1 Ea1 F 12\"}", + "versionstamp": "2015-05-22T20:30:20.208Z" + } +}, +{ + "pk": 5, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series D2", + "sourcefile": "", + "current": true, + "master": 5, + "data": "{\"datePattern\": \"Mi-1,0,9-10 Th 9\", \"location\": \"Room D\", \"people\": [\"Mr Pink\"]}", + "versionstamp": "2015-05-22T20:38:01.420Z" + } +}, +{ + "pk": 6, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series E", + "sourcefile": "", + "current": true, + "master": 6, + "data": "{\"datePattern\": \"Mi1 Tu 2\", \"location\": \"Room E\", \"people\": [\"Ms E\"]}", + "versionstamp": "2015-05-22T20:42:35.392Z" + } +}, +{ + "pk": 7, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series F", + "sourcefile": "", + "current": true, + "master": 7, + "data": "{\"datePattern\": \"Mi5 F 8\", \"location\": \"Room F\", \"people\": [\"Mr F\"]}", + "versionstamp": "2015-05-22T20:44:11.531Z" + } +}, +{ + "pk": 8, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series G", + "sourcefile": "", + "current": true, + "master": 8, + "data": "{\"datePattern\": \"Le4 M 1\", \"location\": \"Room G\", \"people\": [\"Gee Gson\"]}", + "versionstamp": "2015-05-22T20:49:19.770Z" + } +}, +{ + "pk": 9, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Series H", + "sourcefile": "", + "current": true, + "master": 9, + "data": "{\"datePattern\": \"Mi4 Th 5\", \"location\": \"Room H\", \"people\": [\"Dr H\"]}", + "versionstamp": "2015-05-22T20:52:05.040Z" + } +}, +{ + "pk": 10, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Shared Subject A Series", + "sourcefile": "", + "current": true, + "master": 10, + "data": "{\"datePattern\": \"Mi1 Th 9\", \"location\": \"A room\", \"people\": [\"Mr Blue\"]}", + "versionstamp": "2015-05-22T21:10:30.195Z" + } +}, +{ + "pk": 11, + "model": "timetables.eventsource", + "fields": { + "sourceurl": null, + "sourcetype": "pattern", + "title": "Disabled Series", + "sourcefile": "", + "current": true, + "master": 11, + "data": "{\"datePattern\": \"Mi1 Th 9\", \"location\": \"blah\", \"people\": [\"hi\"]}", + "versionstamp": "2015-06-09T20:41:56.977Z" + } +}, +{ + "pk": 1, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T20:08:02.290Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-09T08:00:00Z", + "master": 1, + "location": "Room A", + "uid": "d6560f87-3c7e-49ff-8706-16ba321cb490", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 2, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T20:08:18.570Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-09T08:00:00Z", + "master": 1, + "location": "Room A", + "uid": "d6560f87-3c7e-49ff-8706-16ba321cb490", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 3, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-16T09:00:00Z", + "versionstamp": "2015-05-22T20:08:18.587Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-16T08:00:00Z", + "master": 3, + "location": "Room A", + "uid": "90bcdace-b09c-4860-8e86-1a2527931375", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 4, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T20:15:33.343Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-09T08:00:00Z", + "master": 1, + "location": "Room A", + "uid": "d6560f87-3c7e-49ff-8706-16ba321cb490", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 5, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-16T09:00:00Z", + "versionstamp": "2015-05-22T20:15:33.367Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-16T08:00:00Z", + "master": 3, + "location": "Room B", + "uid": "90bcdace-b09c-4860-8e86-1a2527931375", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\", \"Mr B and Ms C\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 6, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T20:15:45.222Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-09T08:00:00Z", + "master": 1, + "location": "Room A", + "uid": "d6560f87-3c7e-49ff-8706-16ba321cb490", + "data": "{\"type\": \"journal club\", \"people\": [\"Dr A\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 7, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-16T09:00:00Z", + "versionstamp": "2015-05-22T20:15:45.241Z", + "title": "Blah A", + "source": 1, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-16T08:00:00Z", + "master": 3, + "location": "Room B", + "uid": "90bcdace-b09c-4860-8e86-1a2527931375", + "data": "{\"type\": \"field trip\", \"people\": [\"Dr A\", \"Mr B and Ms C\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 8, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-11-03T13:00:00Z", + "versionstamp": "2015-05-22T20:20:42.584Z", + "title": "Thing B", + "source": 2, + "endtz": "Europe/London", + "current": true, + "start": "2014-11-03T12:00:00Z", + "master": 8, + "location": "Room B", + "uid": "ef6ad702-7a52-45cd-8b18-09ffcff660b9", + "data": "{\"type\": \"practical\", \"people\": [\"Mr ABC\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 9, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-02-10T15:46:00Z", + "versionstamp": "2015-05-22T20:24:11.291Z", + "title": "Thing C", + "source": 3, + "endtz": "Europe/London", + "current": true, + "start": "2015-02-10T14:25:00Z", + "master": 9, + "location": "Room C", + "uid": "17e8f1f6-3813-4066-911a-6e6ae4888add", + "data": "{\"type\": \"workshop\", \"people\": [\"abc12\", \"def34\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 10, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-10T12:00:00Z", + "versionstamp": "2015-05-22T20:34:01.717Z", + "title": "D1", + "source": 4, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-10T11:00:00Z", + "master": 10, + "location": "Room D", + "uid": "efd977cd-eb72-42e5-8516-8f6883620f28", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 11, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-10T12:00:00Z", + "versionstamp": "2015-05-22T20:34:17.279Z", + "title": "D1", + "source": 4, + "endtz": "Europe/London", + "current": false, + "start": "2014-10-10T11:00:00Z", + "master": 10, + "location": "Room D", + "uid": "efd977cd-eb72-42e5-8516-8f6883620f28", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 12, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-01-16T13:00:00Z", + "versionstamp": "2015-05-22T20:34:17.295Z", + "title": "D2", + "source": 4, + "endtz": "Europe/London", + "current": false, + "start": "2015-01-16T12:00:00Z", + "master": 12, + "location": "Room D", + "uid": "77aadf4b-2a54-48f0-9d3b-3818471c95cf", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 13, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-10T12:00:00Z", + "versionstamp": "2015-05-22T20:34:31.606Z", + "title": "D1", + "source": 4, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-10T11:00:00Z", + "master": 10, + "location": "Room D", + "uid": "efd977cd-eb72-42e5-8516-8f6883620f28", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 14, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-01-16T13:00:00Z", + "versionstamp": "2015-05-22T20:34:31.625Z", + "title": "D2", + "source": 4, + "endtz": "Europe/London", + "current": true, + "start": "2015-01-16T12:00:00Z", + "master": 12, + "location": "Room D", + "uid": "77aadf4b-2a54-48f0-9d3b-3818471c95cf", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 15, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-04-24T12:00:00Z", + "versionstamp": "2015-05-22T20:34:31.640Z", + "title": "D3", + "source": 4, + "endtz": "Europe/London", + "current": true, + "start": "2015-04-24T11:00:00Z", + "master": 15, + "location": "Room D", + "uid": "634053a1-a29c-4ecc-aa42-61c836208857", + "data": "{\"type\": \"lecture\", \"people\": [\"Prof D\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 16, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-09-25T09:00:00Z", + "versionstamp": "2015-05-22T20:40:04.192Z", + "title": "D2 Thing", + "source": 5, + "endtz": "Europe/London", + "current": true, + "start": "2014-09-25T08:00:00Z", + "master": 16, + "location": "Room D", + "uid": "2ee5f91e-9d3d-46fd-8bf6-f54a5bf15498", + "data": "{\"type\": \"presentation\", \"people\": [\"Mr Pink\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 17, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-02T09:00:00Z", + "versionstamp": "2015-05-22T20:40:04.211Z", + "title": "D2 Thing", + "source": 5, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-02T08:00:00Z", + "master": 17, + "location": "Room D", + "uid": "16b69975-be65-41d2-a12c-6c10fd072e27", + "data": "{\"type\": \"presentation\", \"people\": [\"Mr Pink\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 18, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-12-04T10:00:00Z", + "versionstamp": "2015-05-22T20:40:04.228Z", + "title": "D2 Thing", + "source": 5, + "endtz": "Europe/London", + "current": true, + "start": "2014-12-04T09:00:00Z", + "master": 18, + "location": "Room D", + "uid": "2f3761e1-1205-4262-85ae-2e8a22044bc4", + "data": "{\"type\": \"presentation\", \"people\": [\"Mr Pink\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 19, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-12-11T10:00:00Z", + "versionstamp": "2015-05-22T20:40:04.243Z", + "title": "D2 Thing", + "source": 5, + "endtz": "Europe/London", + "current": true, + "start": "2014-12-11T09:00:00Z", + "master": 19, + "location": "Room D", + "uid": "28874f98-8e5b-40a4-a64a-699010af7f70", + "data": "{\"type\": \"presentation\", \"people\": [\"Mr Pink\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 20, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-14T14:00:00Z", + "versionstamp": "2015-05-22T20:43:53.282Z", + "title": "Event E", + "source": 6, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-14T13:00:00Z", + "master": 20, + "location": "Room E", + "uid": "13764e88-afb0-4407-89fb-9924c9d108a9", + "data": "{\"type\": \"class\", \"people\": [\"Ms E\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 21, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-11-07T09:00:00Z", + "versionstamp": "2015-05-22T20:48:50.190Z", + "title": "Event F", + "source": 7, + "endtz": "Europe/London", + "current": true, + "start": "2014-11-07T08:00:00Z", + "master": 21, + "location": "Room F", + "uid": "75367174-0791-4132-89bb-541e19d64ac4", + "data": "{\"type\": \"seminar\", \"people\": [\"Mr F\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 22, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2015-02-09T14:00:00Z", + "versionstamp": "2015-05-22T20:51:09.211Z", + "title": "Event G", + "source": 8, + "endtz": "Europe/London", + "current": true, + "start": "2015-02-09T13:00:00Z", + "master": 22, + "location": "Room G", + "uid": "288e88be-9fa0-46e1-bb94-cbd8ce39dc3d", + "data": "{\"type\": \"seminar\", \"people\": [\"Gee Gson\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 23, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-30T18:00:00Z", + "versionstamp": "2015-05-22T20:52:59.525Z", + "title": "Event H", + "source": 9, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-30T17:00:00Z", + "master": 23, + "location": "Room H", + "uid": "9ae81002-5484-47ee-a54c-fffe5cf5b8c1", + "data": "{\"type\": \"\", \"people\": [\"Dr H\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 24, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-05-22T21:13:41.890Z", + "title": "Intro to Subject A", + "source": 10, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-09T08:00:00Z", + "master": 24, + "location": "A room", + "uid": "6b4fd2f0-8983-4ca8-ab19-ff7fd1301f37", + "data": "{\"type\": \"workshop\", \"people\": [\"Mr Blue\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 25, + "model": "timetables.event", + "fields": { + "status": 0, + "end": "2014-10-09T09:00:00Z", + "versionstamp": "2015-06-09T20:42:22.051Z", + "title": "Disabled Event", + "source": 11, + "endtz": "Europe/London", + "current": true, + "start": "2014-10-09T08:00:00Z", + "master": null, + "location": "blah", + "uid": "a5078c3b-9d1a-43ee-b4cb-8a8fde013488", + "data": "{\"type\": \"journal club\", \"people\": [\"hi\"]}", + "starttz": "Europe/London" + } +}, +{ + "pk": 1, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 13, + "eventsource": 1, + "annotation": "home" + } +}, +{ + "pk": 2, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 14, + "eventsource": 2, + "annotation": "home" + } +}, +{ + "pk": 3, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 15, + "eventsource": 3, + "annotation": "home" + } +}, +{ + "pk": 4, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 16, + "eventsource": 4, + "annotation": "home" + } +}, +{ + "pk": 5, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 16, + "eventsource": 5, + "annotation": "home" + } +}, +{ + "pk": 6, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 17, + "eventsource": 6, + "annotation": "home" + } +}, +{ + "pk": 7, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 18, + "eventsource": 7, + "annotation": "home" + } +}, +{ + "pk": 8, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 19, + "eventsource": 8, + "annotation": "home" + } +}, +{ + "pk": 9, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 20, + "eventsource": 9, + "annotation": "home" + } +}, +{ + "pk": 10, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 3, + "eventsource": 4, + "annotation": null + } +}, +{ + "pk": 11, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 3, + "eventsource": 8, + "annotation": null + } +}, +{ + "pk": 12, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 3, + "eventsource": 9, + "annotation": null + } +}, +{ + "pk": 13, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 21, + "eventsource": 10, + "annotation": "home" + } +}, +{ + "pk": 14, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 22, + "eventsource": 10, + "annotation": "home" + } +}, +{ + "pk": 15, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 3, + "eventsource": 10, + "annotation": null + } +}, +{ + "pk": 16, + "model": "timetables.eventsourcetag", + "fields": { + "thing": 24, + "eventsource": 11, + "annotation": "home" + } +}, +{ + "pk": 1, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 6, + "annotation": "admin" + } +}, +{ + "pk": 2, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 5, + "annotation": "admin" + } +}, +{ + "pk": 3, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 12, + "annotation": "admin" + } +}, +{ + "pk": 4, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 11, + "annotation": "admin" + } +}, +{ + "pk": 5, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 10, + "annotation": "admin" + } +}, +{ + "pk": 6, + "model": "timetables.thingtag", + "fields": { + "thing": 23, + "targetthing": 23, + "annotation": "disabled" + } +}, +{ + "pk": 7, + "model": "timetables.thingtag", + "fields": { + "thing": 3, + "targetthing": 23, + "annotation": "admin" + } +}, +{ + "pk": 8, + "model": "timetables.thingtag", + "fields": { + "thing": 25, + "targetthing": 25, + "annotation": "disabled" + } +}, +{ + "pk": 9, + "model": "timetables.thingtag", + "fields": { + "thing": 26, + "targetthing": 26, + "annotation": "disabled" + } +} +] diff --git a/fixtures/representative/structure-verification/events.csv b/fixtures/representative/structure-verification/events.csv new file mode 100644 index 0000000..18ec4e8 --- /dev/null +++ b/fixtures/representative/structure-verification/events.csv @@ -0,0 +1,19 @@ +"Tripos Id","Tripos Name","Part Id","Part Name","Subpart Id","Subpart Name","Module Id","Module Name","Series ID","Series Name","Event ID","Title","Type","Start","End","Location","People" +"4","Simple Tripos","5","Part I","","","13","Module A","1","Series A","6","Blah A","journal club","2014-10-09T09:00:00+01:00","2014-10-09T10:00:00+01:00","Room A","Dr A" +"4","Simple Tripos","5","Part I","","","13","Module A","1","Series A","7","Blah A","field trip","2014-10-16T09:00:00+01:00","2014-10-16T10:00:00+01:00","Room B","Dr A#Mr B and Ms C" +"4","Simple Tripos","6","Part II","","","14","Module B","2","Series B","8","Thing B","practical","2014-11-03T12:00:00+00:00","2014-11-03T13:00:00+00:00","Room B","Mr ABC" +"4","Simple Tripos","6","Part II","","","15","Module C","3","Series C","9","Thing C","workshop","2015-02-10T14:25:00+00:00","2015-02-10T15:46:00+00:00","Room C","abc12#def34" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","4","Series D 1","13","D1","lecture","2014-10-10T12:00:00+01:00","2014-10-10T13:00:00+01:00","Room D","Prof D" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","4","Series D 1","14","D2","lecture","2015-01-16T12:00:00+00:00","2015-01-16T13:00:00+00:00","Room D","Prof D" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","4","Series D 1","15","D3","lecture","2015-04-24T12:00:00+01:00","2015-04-24T13:00:00+01:00","Room D","Prof D" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","5","Series D2","16","D2 Thing","presentation","2014-09-25T09:00:00+01:00","2014-09-25T10:00:00+01:00","Room D","Mr Pink" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","5","Series D2","17","D2 Thing","presentation","2014-10-02T09:00:00+01:00","2014-10-02T10:00:00+01:00","Room D","Mr Pink" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","5","Series D2","18","D2 Thing","presentation","2014-12-04T09:00:00+00:00","2014-12-04T10:00:00+00:00","Room D","Mr Pink" +"7","Nested Subject","8","Part I","10","Subject A","16","Module D","5","Series D2","19","D2 Thing","presentation","2014-12-11T09:00:00+00:00","2014-12-11T10:00:00+00:00","Room D","Mr Pink" +"7","Nested Subject","8","Part I","11","Subject B","17","Module E","6","Series E","20","Event E","class","2014-10-14T14:00:00+01:00","2014-10-14T15:00:00+01:00","Room E","Ms E" +"7","Nested Subject","8","Part I","11","Subject B","18","Module F","7","Series F","21","Event F","seminar","2014-11-07T08:00:00+00:00","2014-11-07T09:00:00+00:00","Room F","Mr F" +"7","Nested Subject","9","Part II","12","Subject A","19","Module G","8","Series G","22","Event G","seminar","2015-02-09T13:00:00+00:00","2015-02-09T14:00:00+00:00","Room G","Gee Gson" +"7","Nested Subject","9","Part II","12","Subject A","20","Module H","9","Series H","23","Event H","","2014-10-30T17:00:00+00:00","2014-10-30T18:00:00+00:00","Room H","Dr H" +"7","Nested Subject","8","Part I","10","Subject A","21","Common","10","Shared Subject A Series","24","Intro to Subject A","workshop","2014-10-09T09:00:00+01:00","2014-10-09T10:00:00+01:00","A room","Mr Blue" +"7","Nested Subject","9","Part II","12","Subject A","22","Common","10","Shared Subject A Series","24","Intro to Subject A","workshop","2014-10-09T09:00:00+01:00","2014-10-09T10:00:00+01:00","A room","Mr Blue" +"4","Simple Tripos","23","External Part","","","24","Disabled Module","11","Disabled Series","25","Disabled Event","journal club","2014-10-09T09:00:00+01:00","2014-10-09T10:00:00+01:00","blah","hi" diff --git a/fixtures/representative/structure-verification/structure.json b/fixtures/representative/structure-verification/structure.json new file mode 100644 index 0000000..aea3b49 --- /dev/null +++ b/fixtures/representative/structure-verification/structure.json @@ -0,0 +1,102 @@ +{ + "nodes": { + "4": { + "nodes": { + "25": { + "type": "part", + "nodes": {}, + "data": { + "external": "https://www.example.org/2" + }, + "id": 25, + "name": "External (no events with URL)" + }, + "26": { + "type": "part", + "nodes": {}, + "data": null, + "id": 26, + "name": "External (no events no URL)" + }, + "5": { + "type": "part", + "nodes": {}, + "data": null, + "id": 5, + "name": "Part I" + }, + "6": { + "type": "part", + "nodes": {}, + "data": null, + "id": 6, + "name": "Part II" + }, + "23": { + "type": "part", + "nodes": {}, + "data": { + "external": "https://www.example.org/" + }, + "id": 23, + "name": "External Part" + } + }, + "type": "course", + "id": 4, + "name": "Simple Tripos" + }, + "7": { + "nodes": { + "10": { + "nodes": { + "8": { + "type": "part", + "nodes": {}, + "data": null, + "id": 8, + "name": "Part I" + } + }, + "type": "subject", + "id": 10, + "name": "Subject A" + }, + "11": { + "nodes": { + "8": { + "type": "part", + "nodes": {}, + "data": null, + "id": 8, + "name": "Part I" + } + }, + "type": "subject", + "id": 11, + "name": "Subject B" + }, + "12": { + "nodes": { + "9": { + "type": "part", + "nodes": {}, + "data": null, + "id": 9, + "name": "Part II" + } + }, + "type": "subject", + "id": 12, + "name": "Subject A" + } + }, + "type": "course", + "id": 7, + "name": "Nested Subject" + } + }, + "type": "root", + "id": 0, + "name": "Timetable" +}