示例#1
0
    def test_contains_pos(self):
        """Tests the inside obstacle method."""
        # (lat, lon, rad, height)
        TESTDATA_STATOBST_CONTAINSPOS_OBJ = (-76, 38, 100, 200)
        # (lat, lon, alt)
        TESTDATA_STATOBST_CONTAINSPOS_INSIDE = [
            (-76, 38, 0),
            (-76, 38, -1),
            (-76, 38, 200),
            (-76.0002, 38, 100),
            (-76, 38.0003, 100)
        ]  # yapf: disable
        TESTDATA_STATOBST_CONTAINSPOS_OUTSIDE = [
            (-76, 38, 201),
            (-76.0003, 38, 100),
            (-76, 38.004, 100)
        ]  # yapf: disable

        # Form the test obstacle
        pos = GpsPosition(latitude=TESTDATA_STATOBST_CONTAINSPOS_OBJ[0],
                          longitude=TESTDATA_STATOBST_CONTAINSPOS_OBJ[1])
        pos.save()
        obst = StationaryObstacle(
            gps_position=pos,
            cylinder_radius=TESTDATA_STATOBST_CONTAINSPOS_OBJ[2],
            cylinder_height=TESTDATA_STATOBST_CONTAINSPOS_OBJ[3])
        # Run test points against obstacle
        test_data = [(TESTDATA_STATOBST_CONTAINSPOS_INSIDE, True),
                     (TESTDATA_STATOBST_CONTAINSPOS_OUTSIDE, False)]
        for (cur_data, cur_contains) in test_data:
            for (lat, lon, alt) in cur_data:
                pos = GpsPosition(latitude=lat, longitude=lon)
                pos.save()
                apos = AerialPosition(gps_position=pos, altitude_msl=alt)
                self.assertEqual(obst.contains_pos(apos), cur_contains)
示例#2
0
 def test_clean(self):
     """Tests model validation."""
     obst = StationaryObstacle(latitude=0,
                               longitude=0,
                               cylinder_radius=30,
                               cylinder_height=10)
     obst.full_clean()
示例#3
0
 def test_clean(self):
     """Tests model validation."""
     pos = GpsPosition(latitude=0, longitude=0)
     pos.save()
     obst = StationaryObstacle(gps_position=pos,
                               cylinder_radius=30,
                               cylinder_height=10)
     obst.full_clean()
示例#4
0
 def test_unicode(self):
     """Tests the unicode method executes."""
     pos = GpsPosition(latitude=100, longitude=200)
     pos.save()
     obst = StationaryObstacle(
         gps_position=pos, cylinder_radius=10, cylinder_height=100)
     obst.save()
     self.assertTrue(obst.__unicode__())
 def test_unicode(self):
     """Tests the unicode method executes."""
     pos = GpsPosition(latitude=100, longitude=200)
     pos.save()
     obst = StationaryObstacle(gps_position=pos,
                               cylinder_radius=10,
                               cylinder_height=100)
     obst.save()
     self.assertTrue(obst.__unicode__())
示例#6
0
    def test_evaluate_collision_with_uas(self):
        """Tests the collision with UAS method."""
        TESTDATA_STATOBST_EVALCOLLISION = (
            # Cylinder position
            (-76, 38, 100, 100),
            # Inside positions
            [(-76, 38, 50),
             (-76, 38, 0),
             (-76, 38, 100),
             (-76.0001, 38.0001, 0),
             (-76.0001, 38.0001, 100)],
            # Outside positions
            [(-76.001, 38, 50),
             (-76, 38.002, 150),
             (-76, 38, 150),
             (-76, 38, 101)]
        )  # yapf: disable

        (cyl_details, inside_pos,
         outside_pos) = TESTDATA_STATOBST_EVALCOLLISION
        (cyl_lat, cyl_lon, cyl_height, cyl_rad) = cyl_details

        gpos = GpsPosition(latitude=cyl_lat, longitude=cyl_lon)
        gpos.save()

        obst = StationaryObstacle(gps_position=gpos,
                                  cylinder_radius=cyl_rad,
                                  cylinder_height=cyl_height)
        obst.save()

        inside_logs = []
        outside_logs = []
        logs_to_create = [(inside_pos, inside_logs),
                          (outside_pos, outside_logs)]

        for (positions, log_list) in logs_to_create:
            log_list += self.create_uas_logs(self.user, positions)

        # Assert collisions correctly evaluated
        collisions = [(inside_logs, True), (outside_logs, False)]
        for (log_list, inside) in collisions:
            self.assertEqual(obst.evaluate_collision_with_uas(log_list),
                             inside)
            for log in log_list:
                self.assertEqual(obst.evaluate_collision_with_uas([log]),
                                 inside)

        # Regression test past failed case.
        pos = GpsPosition(latitude=45.4342114, longitude=-71.8538153)
        pos.save()
        obst = StationaryObstacle(gps_position=pos,
                                  cylinder_radius=10.0,
                                  cylinder_height=1300.0)
        logs = self.create_uas_logs(self.user, [
            (45.433839, -71.8523434, 770.013137988),
            (45.4338393, -71.8523446, 769.881926415),
        ])
        self.assertFalse(obst.evaluate_collision_with_uas(logs))
示例#7
0
    def test_determine_interpolated_collision(self):
        utm = distance.proj_utm(zone=18, north=True)

        (olat, olon, orad, oheight) = TESTDATA_STATOBST_INTERP_OBS
        pos = GpsPosition(latitude=olat, longitude=olon)
        pos.save()
        obst = StationaryObstacle(gps_position=pos,
                                  cylinder_radius=orad,
                                  cylinder_height=oheight)

        for (inside, uas_details) in TESTDATA_STATOBST_INTERP_TELEM:
            logs = self.create_uas_logs(self.user, uas_details)
            self.assertEqual(
                obst.determine_interpolated_collision(logs[0], logs[1], utm),
                inside)
示例#8
0
    def setUp(self):
        """Populates an active mission to test a cached view."""
        pos = GpsPosition()
        pos.latitude = 10
        pos.longitude = 10
        pos.save()
        self.pos = pos

        obst = StationaryObstacle()
        obst.gps_position = pos
        obst.cylinder_radius = 10
        obst.cylinder_height = 10
        obst.save()
        self.obst = obst

        config = MissionConfig()
        config.is_active = True
        config.home_pos = pos
        config.emergent_last_known_pos = pos
        config.off_axis_odlc_pos = pos
        config.air_drop_pos = pos
        config.save()
        self.config = config
        config.stationary_obstacles.add(obst)
        config.save()

        self.login_url = reverse('auvsi_suas:login')
        self.obst_url = reverse('auvsi_suas:obstacles')
        self.clear_cache_url = reverse('auvsi_suas:clear_cache')
示例#9
0
    def create_stationary_obstacle(self, lat, lon, radius, height):
        """Create a new StationaryObstacle model.

        Args:
            lat: Latitude of centroid
            lon: Longitude of centroid
            radius: Cylinder radius
            height: Cylinder height

        Returns:
            Saved StationaryObstacle
        """
        gps = GpsPosition(latitude=lat, longitude=lon)
        gps.save()

        obstacle = StationaryObstacle(
            gps_position=gps, cylinder_radius=radius, cylinder_height=height)
        obstacle.save()
        return obstacle
示例#10
0
    def create_stationary_obstacle(self, lat, lon, radius, height):
        """Create a new StationaryObstacle model.

        Args:
            lat: Latitude of centroid
            lon: Longitude of centroid
            radius: Cylinder radius
            height: Cylinder height

        Returns:
            Saved StationaryObstacle
        """
        gps = GpsPosition(latitude=lat, longitude=lon)
        gps.save()

        obstacle = StationaryObstacle(gps_position=gps,
                                      cylinder_radius=radius,
                                      cylinder_height=height)
        obstacle.save()
        return obstacle
 def test_contains_pos(self):
     """Tests the inside obstacle method."""
     # Form the test obstacle
     pos = GpsPosition(latitude=TESTDATA_STATOBST_CONTAINSPOS_OBJ[0],
                       longitude=TESTDATA_STATOBST_CONTAINSPOS_OBJ[1])
     pos.save()
     obst = StationaryObstacle(
         gps_position=pos,
         cylinder_radius=TESTDATA_STATOBST_CONTAINSPOS_OBJ[2],
         cylinder_height=TESTDATA_STATOBST_CONTAINSPOS_OBJ[3])
     # Run test points against obstacle
     test_data = [
         (TESTDATA_STATOBST_CONTAINSPOS_INSIDE, True),
         (TESTDATA_STATOBST_CONTAINSPOS_OUTSIDE, False)
     ]
     for (cur_data, cur_contains) in test_data:
         for (lat, lon, alt) in cur_data:
             pos = GpsPosition(latitude=lat, longitude=lon)
             pos.save()
             apos = AerialPosition(gps_position=pos, altitude_msl=alt)
             self.assertEqual(obst.contains_pos(apos), cur_contains)
示例#12
0
    def setUp(self):
        """Populates an active mission to test a cached view."""
        pos = GpsPosition()
        pos.latitude = 10
        pos.longitude = 10
        pos.save()
        self.pos = pos

        obst = StationaryObstacle()
        obst.gps_position = pos
        obst.cylinder_radius = 10
        obst.cylinder_height = 10
        obst.save()
        self.obst = obst

        config = MissionConfig()
        config.is_active = True
        config.home_pos = pos
        config.emergent_last_known_pos = pos
        config.off_axis_target_pos = pos
        config.air_drop_pos = pos
        config.save()
        self.config = config
        config.stationary_obstacles.add(obst)
        config.save()

        self.login_url = reverse('auvsi_suas:login')
        self.obst_url = reverse('auvsi_suas:obstacles')
        self.clear_cache_url = reverse('auvsi_suas:clear_cache')
示例#13
0
    def test_evaluate_collision_with_uas(self):
        """Tests the collision with UAS method."""
        # Create testing data

        (cyl_details, inside_pos,
         outside_pos) = TESTDATA_STATOBST_EVALCOLLISION
        (cyl_lat, cyl_lon, cyl_height, cyl_rad) = cyl_details

        gpos = GpsPosition(latitude=cyl_lat, longitude=cyl_lon)
        gpos.save()

        obst = StationaryObstacle(gps_position=gpos,
                                  cylinder_radius=cyl_rad,
                                  cylinder_height=cyl_height)
        obst.save()

        inside_logs = []
        outside_logs = []
        logs_to_create = [(inside_pos, inside_logs),
                          (outside_pos, outside_logs)]

        for (positions, log_list) in logs_to_create:
            log_list += self.create_uas_logs(self.user, positions)

        # Assert collisions correctly evaluated
        collisions = [(inside_logs, True), (outside_logs, False)]
        for (log_list, inside) in collisions:
            self.assertEqual(obst.evaluate_collision_with_uas(log_list),
                             inside)
            for log in log_list:
                self.assertEqual(obst.evaluate_collision_with_uas([log]),
                                 inside)
示例#14
0
    def test_json(self):
        """Tests the JSON serialization method."""
        TEST_LAT = 100.10
        TEST_LONG = 200.20
        TEST_RADIUS = 150.50
        TEST_HEIGHT = 75.30

        gpos = GpsPosition(latitude=TEST_LAT, longitude=TEST_LONG)
        gpos.save()
        obstacle = StationaryObstacle(gps_position=gpos,
                                      cylinder_radius=TEST_RADIUS,
                                      cylinder_height=TEST_HEIGHT)
        json_data = obstacle.json()

        self.assertTrue('latitude' in json_data)
        self.assertEqual(json_data['latitude'], TEST_LAT)
        self.assertTrue('longitude' in json_data)
        self.assertEqual(json_data['longitude'], TEST_LONG)
        self.assertTrue('cylinder_radius' in json_data)
        self.assertEqual(json_data['cylinder_radius'], TEST_RADIUS)
        self.assertTrue('cylinder_height' in json_data)
        self.assertEqual(json_data['cylinder_height'], TEST_HEIGHT)
    def test_json(self):
        """Tests the JSON serialization method."""
        TEST_LAT = 100.10
        TEST_LONG = 200.20
        TEST_RADIUS = 150.50
        TEST_HEIGHT = 75.30

        gpos = GpsPosition(latitude=TEST_LAT, longitude=TEST_LONG)
        gpos.save()
        obstacle = StationaryObstacle(gps_position=gpos,
                                      cylinder_radius=TEST_RADIUS,
                                      cylinder_height=TEST_HEIGHT)
        json_data = obstacle.json()

        self.assertTrue('latitude' in json_data)
        self.assertEqual(json_data['latitude'], TEST_LAT)
        self.assertTrue('longitude' in json_data)
        self.assertEqual(json_data['longitude'], TEST_LONG)
        self.assertTrue('cylinder_radius' in json_data)
        self.assertEqual(json_data['cylinder_radius'], TEST_RADIUS)
        self.assertTrue('cylinder_height' in json_data)
        self.assertEqual(json_data['cylinder_height'], TEST_HEIGHT)
示例#16
0
    def test_evaluate_collision_with_uas(self):
        """Tests the collision with UAS method."""
        # Create testing data
        user = User.objects.create_user('testuser', '*****@*****.**',
                                        'testpass')
        user.save()

        (cyl_details, inside_pos,
         outside_pos) = TESTDATA_STATOBST_EVALCOLLISION
        (cyl_lat, cyl_lon, cyl_height, cyl_rad) = cyl_details

        gpos = GpsPosition(latitude=cyl_lat, longitude=cyl_lon)
        gpos.save()

        obst = StationaryObstacle(gps_position=gpos,
                                  cylinder_radius=cyl_rad,
                                  cylinder_height=cyl_height)
        obst.save()

        inside_logs = []
        outside_logs = []
        logs_to_create = [
            (inside_pos, inside_logs), (outside_pos, outside_logs)
        ]

        for (positions, log_list) in logs_to_create:
            for (lat, lon, alt) in positions:
                gpos = GpsPosition(latitude=lat, longitude=lon)
                gpos.save()
                apos = AerialPosition(gps_position=gpos, altitude_msl=alt)
                apos.save()
                log = UasTelemetry(user=user, uas_position=apos, uas_heading=0)
                log.save()
                log_list.append(log)
        # Assert collisions correctly evaluated
        collisions = [(inside_logs, True), (outside_logs, False)]
        for (log_list, inside) in collisions:
            self.assertEqual(
                obst.evaluate_collision_with_uas(log_list), inside)
            for log in log_list:
                self.assertEqual(
                    obst.evaluate_collision_with_uas([log]), inside)
示例#17
0
def create_sample_mission(superuser):
    """Creates a sample mission.

    Args:
        superuser: A superuser account to create mission under.
    Returns:
        MissionConfig for the created mission.
    """
    mission = MissionConfig()

    gpos = GpsPosition(latitude=38.145103, longitude=-76.427856)
    gpos.save()
    mission.home_pos = gpos

    gpos = GpsPosition(latitude=38.145111, longitude=-76.427861)
    gpos.save()
    mission.emergent_last_known_pos = gpos

    gpos = GpsPosition(latitude=38.145111, longitude=-76.427861)
    gpos.save()
    mission.off_axis_odlc_pos = gpos

    gpos = GpsPosition(latitude=38.1458416666667, longitude=-76.426375)
    gpos.save()
    mission.air_drop_pos = gpos

    # All foreign keys must be defined before the first save.
    # All many-to-many must be defined after the first save.
    mission.save()

    bounds = FlyZone(altitude_msl_min=100, altitude_msl_max=750)
    bounds.save()
    # yapf: disable
    pts = [(38.1462694444444, -76.4281638888889),
           (38.1516250000000, -76.4286833333333),
           (38.1518888888889, -76.4314666666667),
           (38.1505944444444, -76.4353611111111),
           (38.1475666666667, -76.4323416666667),
           (38.1446666666667, -76.4329472222222),
           (38.1432555555556, -76.4347666666667),
           (38.1404638888889, -76.4326361111111),
           (38.1407194444444, -76.4260138888889),
           (38.1437611111111, -76.4212055555556),
           (38.1473472222222, -76.4232111111111),
           (38.1461305555556, -76.4266527777778)]
    # yapf: enable
    for ix, (lat, lon) in enumerate(pts):
        wpt = Waypoint(latitude=lat,
                       longitude=lon,
                       altitude_msl=0,
                       order=ix + 1)
        wpt.save()
        bounds.boundary_pts.add(wpt)
    bounds.save()
    mission.fly_zones.add(bounds)

    # yapf: disable
    pts = [(38.146689, -76.426475, 150,
            750), (38.142914, -76.430297, 300,
                   300), (38.149504, -76.433110, 100,
                          750), (38.148711, -76.429061, 300, 750),
           (38.144203, -76.426155, 50, 400), (38.146003, -76.430733, 225, 500)]
    # yapf: enable
    for lat, lon, radius, height in pts:
        obst = StationaryObstacle(latitude=lat,
                                  longitude=lon,
                                  cylinder_radius=radius,
                                  cylinder_height=height)
        obst.save()
        mission.stationary_obstacles.add(obst)

    # yapf: disable
    pts = [(38.1446916666667, -76.4279944444445, 200),
           (38.1461944444444, -76.4237138888889, 300),
           (38.1438972222222, -76.4225500000000, 400),
           (38.1417722222222, -76.4251083333333, 400),
           (38.1453500000000, -76.4286750000000, 300),
           (38.1508972222222, -76.4292972222222, 300),
           (38.1514944444444, -76.4313833333333, 300),
           (38.1505333333333, -76.4341750000000, 300),
           (38.1479472222222, -76.4316055555556, 200),
           (38.1443333333333, -76.4322888888889, 200),
           (38.1433166666667, -76.4337111111111, 300),
           (38.1410944444444, -76.4321555555556, 400),
           (38.1415777777778, -76.4252472222222, 400),
           (38.1446083333333, -76.4282527777778, 200)]
    # yapf: enable
    for ix, (lat, lon, alt) in enumerate(pts):
        wpt = Waypoint(latitude=lat,
                       longitude=lon,
                       altitude_msl=alt,
                       order=ix + 1)
        wpt.save()
        mission.mission_waypoints.add(wpt)

    # yapf: disable
    pts = [(38.1444444444444, -76.4280916666667),
           (38.1459444444444, -76.4237944444445),
           (38.1439305555556, -76.4227444444444),
           (38.1417138888889, -76.4253805555556),
           (38.1412111111111, -76.4322361111111),
           (38.1431055555556, -76.4335972222222),
           (38.1441805555556, -76.4320111111111),
           (38.1452611111111, -76.4289194444444),
           (38.1444444444444, -76.4280916666667)]
    # yapf: enable
    for ix, (lat, lon) in enumerate(pts):
        wpt = Waypoint(latitude=lat,
                       longitude=lon,
                       altitude_msl=0,
                       order=ix + 1)
        wpt.save()
        mission.search_grid_points.add(wpt)

    gpos = GpsPosition(latitude=38.143844, longitude=-76.426469)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.N,
                shape=interop_api_pb2.Odlc.STAR,
                shape_color=interop_api_pb2.Odlc.RED,
                alphanumeric='A',
                alphanumeric_color=interop_api_pb2.Odlc.WHITE)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.141872, longitude=-76.426183)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.NE,
                shape=interop_api_pb2.Odlc.CROSS,
                shape_color=interop_api_pb2.Odlc.BLUE,
                alphanumeric='I',
                alphanumeric_color=interop_api_pb2.Odlc.BLACK)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.142828, longitude=-76.427644)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.E,
                shape=interop_api_pb2.Odlc.QUARTER_CIRCLE,
                shape_color=interop_api_pb2.Odlc.YELLOW,
                alphanumeric='R',
                alphanumeric_color=interop_api_pb2.Odlc.ORANGE)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.144925, longitude=-76.425100)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.SE,
                shape=interop_api_pb2.Odlc.CIRCLE,
                shape_color=interop_api_pb2.Odlc.BROWN,
                alphanumeric='V',
                alphanumeric_color=interop_api_pb2.Odlc.RED)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.146747, longitude=-76.422131)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.S,
                shape=interop_api_pb2.Odlc.TRAPEZOID,
                shape_color=interop_api_pb2.Odlc.WHITE,
                alphanumeric='E',
                alphanumeric_color=interop_api_pb2.Odlc.GRAY)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.144097, longitude=-76.431089)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.SW,
                shape=interop_api_pb2.Odlc.SQUARE,
                shape_color=interop_api_pb2.Odlc.GREEN,
                alphanumeric='H',
                alphanumeric_color=interop_api_pb2.Odlc.BLUE)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.144878, longitude=-76.423681)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.W,
                shape=interop_api_pb2.Odlc.RECTANGLE,
                shape_color=interop_api_pb2.Odlc.PURPLE,
                alphanumeric='I',
                alphanumeric_color=interop_api_pb2.Odlc.GREEN)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.142819, longitude=-76.432375)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.NW,
                shape=interop_api_pb2.Odlc.SEMICIRCLE,
                shape_color=interop_api_pb2.Odlc.ORANGE,
                alphanumeric='C',
                alphanumeric_color=interop_api_pb2.Odlc.YELLOW)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.141639, longitude=-76.429347)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.N,
                shape=interop_api_pb2.Odlc.TRIANGLE,
                shape_color=interop_api_pb2.Odlc.BLACK,
                alphanumeric='L',
                alphanumeric_color=interop_api_pb2.Odlc.PURPLE)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.142478, longitude=-76.424967)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.STANDARD,
                location=gpos,
                orientation=interop_api_pb2.Odlc.NE,
                shape=interop_api_pb2.Odlc.PENTAGON,
                shape_color=interop_api_pb2.Odlc.GRAY,
                alphanumeric='E',
                alphanumeric_color=interop_api_pb2.Odlc.BROWN)
    odlc.save()
    mission.odlcs.add(odlc)

    gpos = GpsPosition(latitude=38.143411, longitude=-76.424489)
    gpos.save()
    odlc = Odlc(mission=mission,
                user=superuser,
                odlc_type=interop_api_pb2.Odlc.EMERGENT,
                location=gpos,
                description='Randy the backpacker.')
    odlc.save()
    mission.odlcs.add(odlc)

    mission.save()
    return mission