def tearDown(self):
        # Create fresh grafana instance (in case test invalidated any tokens, etc.)
        self.grafana = Grafana(self.config)

        # Clear all of the created dashboards
        self.grafana.delete_dashboard_by_name(self.event_name)
        self.grafana.delete_datasource_by_name(self.datasource_name)
Exemple #2
0
    def setUp(self):
        # api keys with admin and viewer level permissions
        self.ADMIN = Grafana.create_api_key(HOST, "admin", "Admin")
        self.VIEWER = Grafana.create_api_key(HOST, "viewer", "Viewer")

        self.login_url = "mercury:EventAccess"
        self.sensor_url = "mercury:sensor"
        self.event_url = "mercury:events"
        self.config_url = "mercury:gfconfig"
        self.config_update_url = "mercury:gfconfig_update"
        self.config_delete_url = "mercury:gfconfig_delete"
        self.config_update_dashboard_url = "mercury:gfconfig_update_dashboard"
        self.config_reset_dashboard_url = "mercury:gfconfig_reset_dashboard"
        self.config_delete_dashboard_url = "mercury:gfconfig_delete_dashboard"
        self.config_add_dashboard_url = "mercury:gfconfig_create_dashboard"
        test_code = EventCodeAccess(event_code="testcode", enabled=True)
        test_code.save()
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        self.gfconfig = GFConfig.objects.create(gf_name="Test",
                                                gf_host=HOST,
                                                gf_token=self.ADMIN,
                                                gf_current=True)
        self.gfconfig.save()
        # Create fresh grafana object
        self.grafana = Grafana(self.gfconfig)

        # Create random name to be used for event and datasource
        self.event_name = self.grafana.generate_random_string(10)
        self.datasource_name = self.grafana.generate_random_string(10)

        # Clear existing dashboard and datasource
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()
def delete_sensor(request, sensor_name):
    """This deletes a sensor from the database based on a button click"""
    sensor_to_delete = AGSensor.objects.get(name=sensor_name)
    sensor_type_to_delete = AGSensorType.objects.get(name=sensor_name)
    if sensor_type_to_delete:

        # delete any sensor panels from grafana
        gfconfigs = GFConfig.objects.all()
        events = AGEvent.objects.all()

        # delete panl from every dashboard of all grafana instances
        for gfconfig in gfconfigs:
            grafana = Grafana(gfconfig)

            # Delete sensor from each event panel
            for event in events:
                try:
                    grafana.delete_panel(sensor_to_delete.name, event)
                except ValueError as error:
                    messages.error(request, error)

        sensor_to_delete.delete()
        sensor_type_to_delete.delete()
    else:
        messages.error(request, sensor_name, " not found!!!")
    return redirect("/sensor")
Exemple #4
0
    def tearDown(self):
        # Create fresh grafana instance (in case test invalidated any tokens, etc.)
        self.grafana = Grafana(self.gfconfig)

        # Clear all of the created dashboards
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()
Exemple #5
0
    def setUp(self):
        # api keys with admin and viewer level permissions
        self.ADMIN = Grafana.create_api_key(HOST, "admin", "Admin")
        self.VIEWER = Grafana.create_api_key(HOST, "viewer", "Viewer")

        self.login_url = "mercury:EventAccess"
        self.sensor_url = "mercury:sensor"
        self.event_url = "mercury:events"
        self.event_delete_url = "mercury:delete_event"
        self.event_update_url = "mercury:update_event"
        self.update_sensor_url = "mercury:update_sensor"
        self.delete_sensor_url = "mercury:delete_sensor"
        self.update_sensor_type_url = "mercury:update_type"
        test_code = EventCodeAccess(event_code="testcode", enabled=True)
        test_code.save()
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # Create fresh grafana object
        self.config = self.create_gfconfig()
        self.grafana = Grafana(self.config)

        # Create random name to be used for event and datasource
        self.event_name = self.grafana.generate_random_string(10)
        self.updated_event_name = self.event_name + " Day Two"
        self.datasource_name = self.grafana.generate_random_string(10)

        # Clear existing dashboard and datasource
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()
Exemple #6
0
    def post(self, request, *args, **kwargs):
        if "submit" in request.POST:
            DB = settings.DATABASES
            config_data = GFConfig(
                gf_name=request.POST.get("gf_name"),
                gf_host=request.POST.get("gf_host"),
                gf_token=request.POST.get("gf_token"),
                gf_db_host=DB["default"]["HOST"] + ":" +
                str(DB["default"]["PORT"]),
                gf_db_name=DB["default"]["NAME"],
                gf_db_username=DB["default"]["USER"],
                gf_db_pw=DB["default"]["PASSWORD"],
            )

            # Create Grafana instance with host and token
            grafana = Grafana(config_data)

            try:
                grafana.create_postgres_datasource()
            except ValueError as error:
                messages.error(request,
                               f"Datasource couldn't be created. {error}")

            try:
                grafana.validate_credentials()
                config_data.gf_current = True  # Deprecated
                # Only save the config if credentials were validated
                config_data.save()

                # If any events exist, add a dashboard for each event
                # If any sensors exist, add them to each event dashboard
                events = AGEvent.objects.all()
                sensors = AGSensor.objects.all()
                for event in events:
                    grafana.create_dashboard(event.name)
                    for sensor in sensors:
                        grafana.add_panel(sensor, event)

            except ValueError as error:
                messages.error(request,
                               f"Grafana initial set up failed: {error}")

            # Prepare an array of dicts with details for each GFConfig (GFConfig object,
            # list of dashboards/forms
            # Retrieve all available GFConfigs
            current_configs = GFConfig.objects.all().order_by("id")

            config_form = GFConfigForm()
            context = {"config_form": config_form, "configs": current_configs}
            return render(request, self.template_name, context)
Exemple #7
0
def configure_dashboard(request, gf_id=None):
    config = GFConfig.objects.get(id=gf_id)

    # Prepare a dict with details for this GFConfig (GFConfig object,
    # list of dashboards/forms
    config_info = dict()
    config_info["config"] = config

    # Create Grafana class to handle this GF instance
    grafana = Grafana(config)

    try:
        grafana.validate_credentials()
        existing_events = grafana.get_all_events()
        current_dashboards = grafana.get_all_dashboards()

    except ValueError as error:
        messages.error(request, f"Cannot connect to Grafana: {error}")
        existing_events = []
        current_dashboards = []

    # Assemble a list of dicts w/: url, sensors, initialized sensor form,
    # and dashboard name

    # Retrieve missing events to pass to the context
    all_events = list(AGEvent.objects.all())
    missing_events = list(set(all_events) - set(existing_events))

    # Prepare an array of dashboards & their sensors to send to the template
    dashboards = []

    for dashboard in current_dashboards:

        dashboard_dict = dict()

        # Get all currently used panels to initialize the form
        existing_sensors = grafana.get_all_sensors(dashboard["title"])

        # Set initial form data so that only existing sensors are checked
        sensor_form = DashboardSensorPanelsForm(
            initial={"sensors": existing_sensors})

        # Retrieve the URL for this dashboard or ""
        dashboard_url = grafana.get_dashboard_url_by_name(dashboard["title"])
        if dashboard_url is None:
            dashboard_url = ""

        # Store everything in a list of dicts
        dashboard_dict["sensor_form"] = sensor_form
        dashboard_dict["dashboard_url"] = dashboard_url
        dashboard_dict["sensors"] = AGSensor.objects.all()
        dashboard_dict["name"] = dashboard["title"]

        dashboards.append(dashboard_dict)

    config_info["dashboards"] = dashboards
    config_info["missing_events"] = missing_events

    context = {"config": config_info}
    return render(request, "gf_dashboards.html", context)
Exemple #8
0
def reset_dashboard(request, gf_id=None):
    gfconfig = GFConfig.objects.filter(id=gf_id).first()

    if gfconfig:
        grafana = Grafana(gfconfig)
        dashboard_name = request.POST.get("dashboard_name")
        sensors = AGSensor.objects.all()
        grafana.update_dashboard_panels(dashboard_name, sensors)
        msg = "Successfully reset dashboard: {}".format(dashboard_name)
        messages.success(request, msg)
    else:
        messages.error(
            request, "Unable to reset dashboard, Grafana instance not found")
    return redirect("/gfconfig/configure/{}".format(gf_id))
Exemple #9
0
def delete_dashboard(request, gf_id=None):
    gfconfig = GFConfig.objects.filter(id=gf_id).first()

    if gfconfig:
        grafana = Grafana(gfconfig)
        dashboard_name = request.POST.get("dashboard_name")
        # try to delete the dashboard
        if grafana.delete_dashboard_by_name(dashboard_name) is False:
            messages.error(request, "Unable to delete dashboard")
        else:
            msg = "Successfully deleted dashboard: {}".format(dashboard_name)
            messages.success(request, msg)
    else:
        messages.error(
            request, "Unable to delete dashboard, Grafana instance not found")
    return redirect("/gfconfig/configure/{}".format(gf_id))
Exemple #10
0
def update_dashboard(request, gf_id=None):
    gfconfig = GFConfig.objects.filter(id=gf_id).first()

    if gfconfig:
        grafana = Grafana(gfconfig)
        dashboard_name = request.POST.get("dashboard_name")
        sensors = request.POST.getlist("sensors")
        sensor_objects = []
        for sensor in sensors:
            sensor = AGSensor.objects.filter(id=sensor).first()
            sensor_objects.append(sensor)
        grafana.update_dashboard_panels(dashboard_name, sensor_objects)
        msg = "Successfully updated dashboard: {}".format(dashboard_name)
        messages.success(request, msg)
    else:
        messages.error(
            request, "Unable to update dashboard, Grafana instance not found")
    return redirect("/gfconfig/configure/{}".format(gf_id))
def update_event(request, event_uuid=None):
    event_to_update = AGEvent.objects.get(uuid=event_uuid)
    if event_to_update:
        new_name = request.POST.get("name")
        new_venue_id = request.POST.get("venue_uuid")
        new_venue_object = AGVenue.objects.get(uuid=new_venue_id)
        new_description = request.POST.get("description")

        # if the name was changed, update all Grafana dashboards with new name
        if new_name != event_to_update.name:
            gfconfigs = GFConfig.objects.all()

            for gfconfig in gfconfigs:

                # Grafana instance using current GFConfig
                grafana = Grafana(gfconfig)

                try:
                    dashboard = grafana.update_dashboard_title(
                        event_to_update, new_name)
                    if dashboard:
                        messages.success(
                            request,
                            f"{gfconfig.gf_name}: Grafana "
                            f"dashboard title updated",
                        )
                except ValueError as error:
                    messages.error(
                        request,
                        f"{gfconfig.gf_name}: Grafana dashboard "
                        f"title not updated: {error} ",
                    )

        # update the AGEvent object
        event_to_update.name = new_name
        event_to_update.venue_uuid = new_venue_object
        event_to_update.description = new_description
        event_to_update.save()

        messages.success(request, "Event updated successfully")

    return redirect("/events")
def delete_event(request, event_uuid=None):
    event_to_delete = AGEvent.objects.get(uuid=event_uuid)

    # delete any dashboards that exist for this event
    gfconfigs = GFConfig.objects.all()

    # Add panel to each grafana instance
    for gfconfig in gfconfigs:

        # Grafana instance using current GFConfig
        grafana = Grafana(gfconfig)

        deleted = grafana.delete_dashboard_by_name(event_to_delete.name)

        if not deleted:
            messages.error(
                request,
                f"Failed to delete Event dashboard from Grafana instance: "
                f"{gfconfig.gf_host}",
            )

    event_to_delete.delete()
    return redirect("/events")
def delete_config(request, gf_id=None):
    config = GFConfig.objects.get(id=gf_id)

    # Create Grafana class to handle this GF instance
    grafana = Grafana(config)

    try:
        grafana.validate_credentials()
        grafana.delete_all_datasources()
        grafana.delete_all_dashboards()
    except ValueError as error:
        messages.error(request, f"Grafana instance not deleted: {error}")
    else:
        messages.success(request, f"Grafana instance deleted successfully")

    config.delete()

    return redirect("/gfconfig")
Exemple #14
0
def create_dashboard(request, gf_id=None):
    gfconfig = GFConfig.objects.filter(id=gf_id).first()
    event_name = request.POST.get("selected_event_name")
    event = AGEvent.objects.filter(name=event_name).first()
    sensors = AGSensor.objects.all()

    if gfconfig and event:

        grafana = Grafana(gfconfig)

        try:
            grafana.create_dashboard(event.name)
            for sensor in sensors:
                grafana.add_panel(sensor, event)
            msg = "Successfully created dashboard: {}".format(event_name)
            messages.success(request, msg)
        except ValueError as error:
            messages.error(f"Unable to add dashboard to Grafana: {error}")

    return redirect("/gfconfig/configure/{}".format(gf_id))
    def post(self, request, *args, **kwargs):
        if "submit-venue" in request.POST:
            post_venue_name = request.POST.get("name")
            post_venue_description = request.POST.get("description")
            post_venue_longitude = request.POST.get("longitude")
            post_venue_latitude = request.POST.get("latitude")
            venue_data = AGVenue(
                name=post_venue_name,
                description=post_venue_description,
                longitude=post_venue_longitude,
                latitude=post_venue_latitude,
            )
            venue_data.save()
        if "submit-event" in request.POST:
            post_event_name = request.POST.get("name")
            post_event_location_id = request.POST.get("venue_uuid")
            venue_object = AGVenue.objects.get(uuid=post_event_location_id)
            post_event_date = request.POST.get("date")
            post_event_description = request.POST.get("description")
            event_data = AGEvent(
                name=post_event_name,
                venue_uuid=venue_object,
                date=post_event_date,
                description=post_event_description,
            )

            event_data.save()

            gfconfig = GFConfig.objects.filter(gf_current=True)

            # only create an event dashboard if a current gfconfig exists
            if gfconfig.count() > 0:
                dashboard = None
                # create a dashboard with the same name as the event
                config = gfconfig[0]
                try:
                    grafana = Grafana(config)
                    dashboard = grafana.create_dashboard(post_event_name)
                except ValueError as error:
                    # pass any failure message from the API to the UI
                    messages.error(
                        request,
                        f"Grafana dashboard for this event was not "
                        f"created: {error}",
                    )

                # if a dashboard was created successfully, add panels to it
                if dashboard:
                    # create a panel for each sensor
                    sensors = AGSensor.objects.all()
                    for sensor in sensors:
                        try:
                            grafana.add_panel(sensor, event_data)
                        except ValueError as error:
                            # pass any error messages from the API to the UI
                            messages.error(request, error)

        events = AGEvent.objects.all().order_by("uuid")
        venues = AGVenue.objects.all().order_by("uuid")
        event_form = EventForm()
        venue_form = VenueForm()
        context = {
            "event_form": event_form,
            "venue_form": venue_form,
            "events": events,
            "venues": venues,
        }
        return render(request, self.template_name, context)
Exemple #16
0
class TestGFConfig(TestCase):
    TESTCODE = "testcode"

    test_sensor_name = "Wind Sensor"
    test_sensor_type = "Dual wind"
    test_sensor_format = {
        "left_gust": {
            "unit": "km/h",
            "format": "float"
        },
        "right_gust": {
            "unit": "km/h",
            "format": "float"
        },
    }
    test_sensor_graph_type = "graph"

    test_event_data = {
        "name": "Sunny Day Test Drive",
        "date": datetime.datetime(2020, 2, 2, 20, 21, 22),
        "description": "A very progressive test run at \
                        Sunnyside Daycare's Butterfly Room.",
        "location": "New York, NY",
    }

    test_venue_data = {
        "name": "Venue 1",
        "description": "foo",
        "latitude": 100,
        "longitude": 200,
    }

    def create_venue_and_event(self, event_name):
        venue = AGVenue.objects.create(
            name=self.test_venue_data["name"],
            description=self.test_venue_data["description"],
            latitude=self.test_venue_data["latitude"],
            longitude=self.test_venue_data["longitude"],
        )
        venue.save()

        event = AGEvent.objects.create(
            name=event_name,
            date=self.test_event_data["date"],
            description=self.test_event_data["description"],
            venue_uuid=venue,
        )
        event.save()

        return event

    def setUp(self):
        # api keys with admin and viewer level permissions
        self.ADMIN = Grafana.create_api_key(HOST, "admin", "Admin")
        self.VIEWER = Grafana.create_api_key(HOST, "viewer", "Viewer")

        self.login_url = "mercury:EventAccess"
        self.sensor_url = "mercury:sensor"
        self.event_url = "mercury:events"
        self.config_url = "mercury:gfconfig"
        self.config_update_url = "mercury:gfconfig_update"
        self.config_delete_url = "mercury:gfconfig_delete"
        self.config_update_dashboard_url = "mercury:gfconfig_update_dashboard"
        self.config_reset_dashboard_url = "mercury:gfconfig_reset_dashboard"
        self.config_delete_dashboard_url = "mercury:gfconfig_delete_dashboard"
        self.config_add_dashboard_url = "mercury:gfconfig_create_dashboard"
        test_code = EventCodeAccess(event_code="testcode", enabled=True)
        test_code.save()
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        self.gfconfig = GFConfig.objects.create(gf_name="Test",
                                                gf_host=HOST,
                                                gf_token=self.ADMIN,
                                                gf_current=True)
        self.gfconfig.save()
        # Create fresh grafana object
        self.grafana = Grafana(self.gfconfig)

        # Create random name to be used for event and datasource
        self.event_name = self.grafana.generate_random_string(10)
        self.datasource_name = self.grafana.generate_random_string(10)

        # Clear existing dashboard and datasource
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()

    def tearDown(self):
        # Create fresh grafana instance (in case test invalidated any tokens, etc.)
        self.grafana = Grafana(self.gfconfig)

        # Clear all of the created dashboards
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()

    def _get_with_event_code(self, url, event_code):
        self.client.get(reverse(self.login_url))
        self.client.post(reverse(self.login_url),
                         data={"eventcode": event_code})
        response = self.client.get(reverse(url))
        session = self.client.session
        return response, session

    def test_config_view_get_success(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        response = self.client.get(reverse(self.config_url))
        self.assertEqual(200, response.status_code)

    def test_config_view_get_existing_dashboard_displayed(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        venue = AGVenue.objects.create(
            name=self.test_venue_data["name"],
            description=self.test_venue_data["description"],
            latitude=self.test_venue_data["latitude"],
            longitude=self.test_venue_data["longitude"],
        )
        venue.save()

        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()

        # Send a request to create an event (should trigger the creation of a
        # grafana dashboard of the same name)
        self.client.post(
            reverse(self.event_url),
            data={
                "submit-event": "",
                "name": self.event_name,
                "date": self.test_event_data["date"],
                "description": self.test_event_data["description"],
                "venue_uuid": venue.uuid,
            },
        )
        response = self.client.get("/gfconfig/configure/{}".format(
            self.gfconfig.id))
        self.assertContains(response, self.event_name)
        self.assertContains(response, sensor.name)

    def test_config_post_success(self):
        # Delete GFConfig used for the tests (will interfere otherwise)
        self.gfconfig.delete()
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        response = self.client.post(
            reverse(self.config_url),
            data={
                "submit": "",
                "gf_name": "Test Grafana Instance",
                "gf_host": HOST,
                "gf_token": self.ADMIN,
                "gf_username": "******",
                "gf_password": "******",
            },
        )
        self.assertEqual(302, response.status_code)

        # Restore GFConfig instance used for the tests
        self.gfconfig = GFConfig.objects.create(gf_name="Test",
                                                gf_host=HOST,
                                                gf_token=self.ADMIN,
                                                gf_current=True)
        self.gfconfig.save()

        gfconfig = GFConfig.objects.filter(gf_name="Test Grafana Instance")
        self.assertTrue(gfconfig.count() > 0)
        self.assertTrue(gfconfig[0].gf_name == "Test Grafana Instance")
        self.assertTrue(gfconfig[0].gf_host == HOST)
        self.assertTrue(gfconfig[0].gf_token == self.ADMIN)

    def test_config_post_fail_invalid_api_key(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        response = self.client.post(
            reverse(self.config_url),
            data={
                "submit": "",
                "gf_name": "Test Grafana Instance",
                "gf_host": HOST,
                "gf_token": "abcde",
            },
        )
        self.assertEqual(302, response.status_code)

        gfconfig = GFConfig.objects.filter(gf_name="Test Grafana Instance")
        self.assertTrue(gfconfig.count() == 0)

    def test_config_post_fail_insufficient_permissions(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        response = self.client.post(
            reverse(self.config_url),
            data={
                "submit": "",
                "gf_name": "Test Grafana Instance",
                "gf_host": HOST,
                "gf_token": self.VIEWER,
                "gf_username": "******",
                "gf_password": "******",
            },
        )
        self.assertEqual(302, response.status_code)

        gfconfig = GFConfig.objects.filter(gf_name="Test Grafana Instance")
        self.assertTrue(gfconfig.count() == 0)

    def test_delete_config(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        GFConfig.objects.all().delete()

        gfconfig = GFConfig.objects.create(gf_name="Test Grafana Instance",
                                           gf_host=HOST,
                                           gf_token=self.ADMIN)
        gfconfig.save()
        gfconfig = GFConfig.objects.filter(
            gf_name="Test Grafana Instance").first()

        self.client.post(
            os.path.join(
                reverse(self.config_delete_url, kwargs={"gf_id":
                                                        gfconfig.id})),
            data={
                "submit": "",
                "gf_name": "Test Grafana Instance",
                "gf_host": HOST,
                "gf_token": self.ADMIN,
                "gf_username": "******",
                "gf_password": "******",
            },
        )
        gfconfig = GFConfig.objects.filter(gf_name="Test Grafana Instance")
        self.assertTrue(gfconfig.count() == 0)

    # test that GFConfig.gf_current can be set to True using the update view
    def test_update_config(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        GFConfig.objects.all().delete()

        gfconfig = GFConfig.objects.create(
            gf_name="Test Grafana Instance",
            gf_host=HOST,
            gf_token=self.ADMIN,
            gf_current=False,
        )
        gfconfig.save()

        self.client.post(
            os.path.join(
                reverse(self.config_update_url, kwargs={"gf_id":
                                                        gfconfig.id})))

        gfconfig = GFConfig.objects.all().first()
        self.assertEquals(gfconfig.gf_current, True)

    def test_config_post_event_exists_dashboard_created(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        self.create_venue_and_event(self.event_name)

        # Delete GFConfig used for the test (will interfere otherwise)
        self.gfconfig.delete()

        response = self.client.post(
            reverse(self.config_url),
            data={
                "submit": "",
                "gf_name": "Test Grafana Instance",
                "gf_host": HOST,
                "gf_token": self.ADMIN,
                "gf_username": "******",
                "gf_password": "******",
            },
        )
        self.assertEqual(302, response.status_code)

        # Restore GFConfig instance used for the tests
        self.gfconfig = GFConfig.objects.create(gf_name="Test",
                                                gf_host=HOST,
                                                gf_token=self.ADMIN,
                                                gf_current=True)
        self.gfconfig.save()

        # check that dashboard was created with same name as event
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        self.assertTrue(dashboard)
        self.assertEquals(dashboard["dashboard"]["title"], self.event_name)

    def test_config_post_event_exists_dashboard_created_with_sensor(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()

        self.create_venue_and_event(self.event_name)

        # Delete GFConfig used for the test (will interfere otherwise)
        self.gfconfig.delete()

        response = self.client.post(
            reverse(self.config_url),
            data={
                "submit": "",
                "gf_name": "Test Grafana Instance",
                "gf_host": HOST,
                "gf_token": self.ADMIN,
                "gf_username": "******",
                "gf_password": "******",
            },
        )
        self.assertEqual(302, response.status_code)

        # Restore GFConfig instance used for the tests
        self.gfconfig = GFConfig.objects.create(gf_name="Test",
                                                gf_host=HOST,
                                                gf_token=self.ADMIN,
                                                gf_current=True)
        self.gfconfig.save()

        # check that dashboard was created with expected panel
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        self.assertTrue(dashboard)
        self.assertEquals(dashboard["dashboard"]["title"], self.event_name)
        # panels should have been created
        # querying like this because the returned dashboard object may have no panels
        # attribute, so trying to retrieve dashboard["panels"] could throw a key error
        panels = dashboard["dashboard"].get("panels", None)
        self.assertTrue(panels)
        self.assertTrue(len(panels) == 1)
        panel = panels[0]
        self.assertEquals(panel["title"], self.test_sensor_name)

    def test_update_dashboard_panels_remove_all_single_gfconfig(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # Create an event
        event = self.create_venue_and_event(self.event_name)

        # Create a dashboard
        self.grafana.create_dashboard(self.event_name)

        # Add a panel to the dashboard
        #   Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()
        #   Add a sensor panel
        self.grafana.add_panel(sensor, event)

        # Update dashboard with empty list of sensors
        self.client.post(
            reverse(self.config_update_dashboard_url,
                    kwargs={"gf_id": self.gfconfig.id}),
            data={
                "dashboard_name": self.event_name,
                "sensors": []
            },
        )

        # Query dashboard
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        self.assertTrue(dashboard)

        # Retrieve current panels
        try:
            panels = dashboard["dashboard"]["panels"]
        except KeyError:
            panels = []

        # Confirm panels were deleted
        self.assertEquals(panels, [])

    def test_update_dashboard_panels_keep_all_panels_single_gfconfig(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        self.create_venue_and_event(self.event_name)

        # create a dashboard
        self.grafana.create_dashboard(self.event_name)

        # add a panel to the dashboard
        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()

        sensors = AGSensor.objects.all()
        sensor_ids = []
        for sensor in sensors:
            sensor_ids.append(sensor.uuid)

        self.client.post(
            reverse(self.config_update_dashboard_url,
                    kwargs={"gf_id": self.gfconfig.id}),
            data={
                "dashboard_name": self.event_name,
                "sensors": sensor_ids
            },
        )

        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertTrue(dashboard)

        # Retrieve current panels
        try:
            panels = dashboard["dashboard"]["panels"]
        except KeyError:
            panels = []

        self.assertEquals(len(panels), 1)

    def test_update_dashboard_panels_keep_subset_of_panels_single_gfconfig(
            self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        self.create_venue_and_event(self.event_name)

        # create a dashboard
        self.grafana.create_dashboard(self.event_name)

        # add a panel to the dashboard
        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        # Create 5 sensors
        for i in range(5):
            sensor = AGSensor.objects.create(name=self.test_sensor_name + "i",
                                             type_id=sensor_type)
            sensor.save()

        # Retrieve sensor ids for the first 2 sensors
        sensor_ids = []
        sensors = AGSensor.objects.all()
        for i in range(2):
            sensor_ids.append(sensors[i].uuid)

        # Post to update the dashboard with 2 sensor panels
        self.client.post(
            reverse(self.config_update_dashboard_url,
                    kwargs={"gf_id": self.gfconfig.id}),
            data={
                "dashboard_name": self.event_name,
                "sensors": sensor_ids
            },
        )

        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertTrue(dashboard)

        # Retrieve current panels
        try:
            panels = dashboard["dashboard"]["panels"]
        except KeyError:
            panels = []

        self.assertEquals(len(panels), 2)
        for i in range(2):
            self.assertEquals(panels[i]["title"], sensors[i].name)

    def test_reset_dashboard_panels_single_gfconfig(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # update dashboard with a subset of panels, then restore all panels by using
        # reset
        self.create_venue_and_event(self.event_name)

        # create a dashboard
        self.grafana.create_dashboard(self.event_name)

        # add a panel to the dashboard
        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        # Create 5 sensors
        for i in range(5):
            sensor = AGSensor.objects.create(name=self.test_sensor_name + "i",
                                             type_id=sensor_type)
            sensor.save()

        # Retrieve sensor ids for the first 2 sensors
        sensor_ids_partial = []
        sensors = AGSensor.objects.all()
        for i in range(2):
            sensor_ids_partial.append(sensors[i].uuid)

        # Post to update the dashboard with 2 sensor panels
        self.client.post(
            reverse(self.config_update_dashboard_url,
                    kwargs={"gf_id": self.gfconfig.id}),
            data={
                "dashboard_name": self.event_name,
                "sensors": sensor_ids_partial
            },
        )

        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertTrue(dashboard)

        # Retrieve current panels
        try:
            panels = dashboard["dashboard"]["panels"]
        except KeyError:
            panels = []

        self.assertEquals(len(panels), 2)

        sensor_ids = []
        sensors = AGSensor.objects.all()
        for sensor in sensors:
            sensor_ids.append(sensor.uuid)

        # Post to reset the dashboard with all sensor panels
        self.client.post(
            reverse(self.config_reset_dashboard_url,
                    kwargs={"gf_id": self.gfconfig.id}),
            data={"dashboard_name": self.event_name},
        )

        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertTrue(dashboard)

        # Retrieve current panels
        try:
            panels = dashboard["dashboard"]["panels"]
        except KeyError:
            panels = []

        self.assertEquals(len(panels), 5)
        for sensor in sensors:
            self.assertEquals(panels[i]["title"], sensor.name)

    def test_delete_dashboard_single_gfconfig(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # update dashboard with a subset of panels, then restore all panels by using
        # reset
        self.create_venue_and_event(self.event_name)

        # create a dashboard
        self.grafana.create_dashboard(self.event_name)

        # Post to update the dashboard with 2 sensor panels
        self.client.post(
            reverse(self.config_delete_dashboard_url,
                    kwargs={"gf_id": self.gfconfig.id}),
            data={"dashboard_name": self.event_name},
        )

        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        # No dashboard should exist with this name
        self.assertFalse(dashboard)

    # User can post to a create_dashboard view in GFConfig in order to add an event
    # dashboard to Grafana
    def test_add_event_dashboard_through_gfconfig_view(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # Create an event
        self.create_venue_and_event(self.event_name)

        # User posts to add_dashboard view
        self.client.post(
            reverse(self.config_add_dashboard_url,
                    kwargs={"gf_id": self.gfconfig.id}),
            data={"selected_event_name": self.event_name},
        )

        # Confirm dashboard added to Grafana
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        self.assertTrue(dashboard)

    def test_add_event_dashboard_through_gfconfig_sensor_panels_added(self):
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # Create an event
        self.create_venue_and_event(self.event_name)

        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()

        # User posts to add_dashboard view
        self.client.post(
            reverse(self.config_add_dashboard_url,
                    kwargs={"gf_id": self.gfconfig.id}),
            data={"selected_event_name": self.event_name},
        )

        # Confirm dashboard added with panel for existing sensor
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        self.assertTrue(dashboard)

        # Retrieve current panels
        try:
            panels = dashboard["dashboard"]["panels"]
        except KeyError:
            panels = []

        self.assertEquals(len(panels), 1)
        self.assertEquals(panels[0]["title"], sensor.name)
    def post(self, request, *args, **kwargs):
        if "submit" not in request.POST:
            return

        DB = settings.DATABASES
        gf_host = request.POST.get("gf_host")
        gf_name = request.POST.get("gf_name")
        gf_username = request.POST.get("gf_username")
        gf_password = request.POST.get("gf_password")
        gf_token = request.POST.get("gf_token")

        # check whether gf_host already in use
        existing_host = GFConfig.objects.filter(gf_host=gf_host)
        if "update-config" not in request.POST and existing_host:
            messages.error(
                request,
                "Hostname {} already in use".format(gf_host),
            )
            return redirect("/gfconfig")

        # a test is calling this with known gf_token
        if not gf_token:
            auth_url = make_auth_url(gf_host, gf_username, gf_password)
            try:
                gf_token = Grafana.create_api_key(auth_url,
                                                  "mercury-auto-admin",
                                                  "Admin")
            except ValueError as error:
                messages.error(
                    request,
                    "Failed to create API token: {}".format(error),
                )
                return redirect("/gfconfig")

            # the user is submitting an update form
            if "update-config" in request.POST:
                existing_host.update(
                    gf_username=gf_username,
                    gf_password=gf_password,
                    gf_token=gf_token,
                )
                messages.success(request,
                                 "Updated Grafana host: {}".format(gf_host))
                return redirect("/gfconfig")

        # new gfconfig record
        config_data = GFConfig(
            gf_name=gf_name,
            gf_host=gf_host,
            gf_username=gf_username,
            gf_password=gf_password,
            gf_token=gf_token,
            gf_db_host=DB["default"]["HOST"] + ":" +
            str(DB["default"]["PORT"]),
            gf_db_name=DB["default"]["NAME"],
            gf_db_username=DB["default"]["USER"],
            gf_db_pw=DB["default"]["PASSWORD"],
        )

        # Create Grafana instance with host and token
        grafana = Grafana(config_data)

        try:
            grafana.validate_credentials()
            grafana.create_postgres_datasource()
        except ValueError as error:
            messages.error(request, error)
        try:

            config_data.gf_current = True  # Deprecated
            # Only save the config if credentials were validated
            config_data.save()

            # If any events exist, add a dashboard for each event
            # If any sensors exist, add them to each event dashboard
            events = AGEvent.objects.all()
            sensors = AGSensor.objects.all()
            for event in events:
                grafana.create_dashboard(event.name)
                for sensor in sensors:
                    grafana.add_panel(sensor, event)

        except ValueError as error:
            messages.error(request, f"Grafana initial set up failed: {error}")

        messages.success(request, "Created Grafana Host: {}".format(gf_name))
        return redirect("/gfconfig")
    def post(self, request, *args, **kwargs):
        sensor_name = request.POST.get("sensor-name")  # name = type name
        field_names = request.POST.getlist("field-names")
        field_types = request.POST.getlist("data-types")
        field_units = request.POST.getlist("units")
        sensor_name, field_names = remove_whitespace_caps(sensor_name, field_names)
        graph_type = request.POST.get("sensor-graph-type")

        if "edit_sensor" in request.POST:
            new_name = request.POST.get("sensor-name-updated")
            new_name, field_names = remove_whitespace_caps(new_name, field_names)
            new = True
            if new_name == sensor_name:
                new = False
            valid, request = validate_inputs(new_name, field_names, request, new)
            if graph_type == "map" and len(field_names) != 2:
                messages.error(
                    request,
                    "Map panels must have exactly 2 fields for "
                    "latitude and longitude GPS coordinates. Update "
                    "the sensor fields or change the Graph Type.",
                )
                valid = False
            new_format = generate_sensor_format(field_names, field_types, field_units)
            if valid:
                sensor_to_update = AGSensor.objects.get(name=sensor_name)
                prev_name = sensor_to_update.name

                sensor_type_to_update = AGSensorType.objects.get(name=sensor_name)
                prev_format = sensor_type_to_update.format
                prev_graph_type = sensor_type_to_update.graph_type
                sensor_to_update.name = new_name
                sensor_type_to_update.name = new_name
                sensor_type_to_update.format = new_format
                sensor_type_to_update.graph_type = graph_type
                sensor_to_update.save()
                sensor_type_to_update.save()

                # update sensor panel in each grafana instance
                gfconfigs = GFConfig.objects.all()
                events = AGEvent.objects.all()

                name_changed = True if new_name != prev_name else False
                format_changed = True if new_format != prev_format else False
                graph_type_changed = True if graph_type != prev_graph_type else False

                if name_changed and not (format_changed or graph_type_changed):
                    for gfconfig in gfconfigs:
                        grafana = Grafana(gfconfig)

                        for event in events:
                            # Update sensor panel in each event dashboard
                            # instance
                            try:
                                grafana.update_panel_title(event, prev_name, new_name)
                            except ValueError as error:
                                messages.error(request, error)
                    messages.success(
                        request, f"Grafana panels updated based on sensor changes"
                    )
                elif format_changed or graph_type_changed:
                    if format_changed:
                        # Delete any existing measurement data for the sensor
                        AGMeasurement.objects.filter(
                            sensor_id=sensor_to_update.id
                        ).delete()

                    for gfconfig in gfconfigs:
                        grafana = Grafana(gfconfig)

                        for event in events:
                            # Update sensor panel in each event dashboard
                            # instance
                            try:
                                grafana.update_panel_sensor(
                                    event, prev_name, sensor_to_update
                                )
                            except ValueError as error:
                                messages.error(request, error)
                    messages.success(
                        request, f"Grafana panels updated based on sensor changes"
                    )
                else:
                    messages.error(request, f"No changes detected - no updates made")

        if "submit_new_sensor" in request.POST:
            valid, request = validate_inputs(
                sensor_name, field_names, request, new=True
            )
            sensor_format = generate_sensor_format(
                field_names, field_types, field_units
            )
            graph_type = request.POST.get("sensor-graph-type")
            if valid:
                """1) The structure of the models (database API) is confusing and we hide
                 the confusing details from the user. 2) Note that we have to first
                 create a sensor type, save it then create a sensor which takes
                 type as an input 3) Sensor types and sensors have the same name.
                 They are the same concept and should not be separated. Separating
                 sensors from sensor types will likely cause bad things to happen...
                 """
                new_type = AGSensorType.objects.create(
                    name=sensor_name,
                    processing_formula=0,
                    format=sensor_format,
                    graph_type=graph_type,
                )
                new_type.save()

                sensor_type = AGSensorType.objects.get(name=sensor_name)

                new_sensor = AGSensor.objects.create(
                    name=sensor_name, type_id=sensor_type
                )
                new_sensor.save()

                # Add Sensor panel to the Active Event dashboard in each Grafana
                # instances
                gfconfigs = GFConfig.objects.all()

                # Retrieve the events
                events = AGEvent.objects.all()

                for event in events:
                    # Add panel to each grafana instance
                    for gfconfig in gfconfigs:
                        # Grafana instance using current GFConfig
                        grafana = Grafana(gfconfig)

                        # Add the Sensor Panel to each event
                        try:
                            grafana.add_panel(new_sensor, event)
                        except ValueError as error:
                            messages.error(
                                request,
                                f"Failed to add panel to active dashboard: {error}",
                            )
                        else:
                            messages.success(
                                request,
                                "Sensor panel added to Grafana for the active event",
                            )

        # gather sensors and sensor types (which should be the same) and render them
        types = AGSensorType.objects.all()
        sensors = AGSensor.objects.all()
        graph_types = AGSensorType.GRAPH_CHOICES
        context = {
            "sensor_types": types,
            "sensors": sensors,
            "graph_types": graph_types,
        }
        return render(request, self.template_name, context)
Exemple #19
0
class TestGrafana(TestCase):
    TESTCODE = "testcode"

    title = "Bar"

    test_sensor_name = "Wind Sensor"
    test_sensor_name_update = "Another Name"
    test_sensor_type = "Dual wind"
    test_sensor_format = {
        "left_gust": {
            "unit": "km/h",
            "format": "float"
        },
        "right_gust": {
            "unit": "km/h",
            "format": "float"
        },
    }
    test_sensor_graph_type = "graph"

    field_name_1 = "test-field-1"
    field_name_2 = "test-field-2"
    data_type_1 = "test-data-type-1"
    data_type_2 = "test-data-type-2"
    unit_1 = "test-unit-1"
    unit_2 = "test-unit-2"

    test_sensor = {
        "name": test_sensor_name,
        "processing formula": 0,
        "field-names": [field_name_1, field_name_2],
        "data-types": [data_type_1, data_type_2],
        "units": [unit_1, unit_2],
    }

    test_sensor_format_update = {
        "fuel reading": {
            "unit": "m",
            "format": "float"
        },
    }

    test_event_data = {
        "name": "Sunny Day Test Drive",
        "date": datetime.datetime(2020, 2, 2, 20, 21, 22),
        "description": "A very progressive test run at \
                    Sunnyside Daycare's Butterfly Room.",
        "location": "New York, NY",
    }

    test_event_data_update = {
        "name": "Another Day Test Drive",
        "date": datetime.datetime(2020, 3, 3, 20, 21, 22),
        "description": "A very modern test run at \
                        my backyard.",
        "location": "Buffalo, NY",
    }

    test_venue_data = {
        "name": "Venue 1",
        "description": "foo",
        "latitude": 100,
        "longitude": 200,
    }

    def create_gfconfig(self):
        config = GFConfig.objects.create(
            gf_host=HOST,
            gf_token=self.ADMIN,
            gf_db_host=DB_HOSTNAME,
            gf_db_name=DB_NAME,
            gf_db_username=DB_USERNAME,
            gf_db_pw=DB_PASSWORD,
            gf_current=True,
        )
        config.save()
        return config

    # Returns event
    def create_venue_and_event(self, event_name):
        venue = AGVenue.objects.create(
            name=self.test_venue_data["name"],
            description=self.test_venue_data["description"],
            latitude=self.test_venue_data["latitude"],
            longitude=self.test_venue_data["longitude"],
        )
        venue.save()

        event = AGEvent.objects.create(
            name=event_name,
            date=self.test_event_data["date"],
            description=self.test_event_data["description"],
            venue_uuid=venue,
        )
        event.save()

        return event

    def setUp(self):
        # api keys with admin and viewer level permissions
        self.ADMIN = Grafana.create_api_key(HOST, "admin", "Admin")
        self.VIEWER = Grafana.create_api_key(HOST, "viewer", "Viewer")

        self.login_url = "mercury:EventAccess"
        self.sensor_url = "mercury:sensor"
        self.event_url = "mercury:events"
        self.event_delete_url = "mercury:delete_event"
        self.event_update_url = "mercury:update_event"
        self.update_sensor_url = "mercury:update_sensor"
        self.delete_sensor_url = "mercury:delete_sensor"
        self.update_sensor_type_url = "mercury:update_type"
        test_code = EventCodeAccess(event_code="testcode", enabled=True)
        test_code.save()
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # Create fresh grafana object
        self.config = self.create_gfconfig()
        self.grafana = Grafana(self.config)

        # Create random name to be used for event and datasource
        self.event_name = self.grafana.generate_random_string(10)
        self.updated_event_name = self.event_name + " Day Two"
        self.datasource_name = self.grafana.generate_random_string(10)

        # Clear existing dashboard and datasource
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()

    def tearDown(self):
        # Create fresh grafana instance (in case test invalidated any tokens, etc.)
        self.grafana = Grafana(self.config)

        # Clear all of the created dashboards
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()

    def _get_with_event_code(self, url, event_code):
        self.client.get(reverse(self.login_url))
        self.client.post(reverse(self.login_url),
                         data={"eventcode": event_code})
        response = self.client.get(reverse(url))
        session = self.client.session
        return response, session

    def test_get_dashboard_with_uid_success(self):
        # Should return True if create_dashboard was successful
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Query new dashboard
        uid = dashboard["uid"]
        fetched_dashboard = self.grafana.get_dashboard_with_uid(uid)

        # Confirm params of new dashboard (slug, uid, title)
        self.assertTrue(fetched_dashboard)
        self.assertTrue(fetched_dashboard["meta"])
        self.assertTrue(fetched_dashboard["meta"]["slug"],
                        self.event_name.lower())
        self.assertTrue(fetched_dashboard["dashboard"])
        self.assertTrue(fetched_dashboard["dashboard"]["uid"], uid)
        self.assertTrue(fetched_dashboard["dashboard"]["title"],
                        self.event_name)

    def test_get_dashboard_url_success(self):
        self.grafana.create_dashboard(self.event_name)
        self.assertTrue(self.grafana.get_dashboard_url_by_name(
            self.event_name))

    def test_get_dashboard_url_none(self):
        self.assertIsNone(
            self.grafana.get_dashboard_url_by_name(self.event_name + "foo"))

    def test_get_dashboard_with_uid_fail(self):
        # Create a random UID to search for
        uid = self.grafana.generate_random_string(10)

        # Try and fetch random UID (should return None)
        fetched_dashboard = self.grafana.get_dashboard_with_uid(uid)
        self.assertFalse(fetched_dashboard)

    def test_create_grafana_dashboard_success(self):
        # Should return an object with dashboard credentials
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

    def test_create_grafana_dashboard_verify_new_dashboard_contents(self):
        # Should return an object with dashboard credentials
        dashboard = self.grafana.create_dashboard(self.event_name)

        # Confirm that an object was returned
        self.assertTrue(dashboard)
        # Check for success status message
        self.assertEquals(dashboard["status"], "success")
        # Check that dashboard has correct name
        self.assertEquals(dashboard["slug"], self.event_name.lower())

        # Retrieve UID for new dashboard, use to query new dashboard
        uid = dashboard["uid"]

        # Confirm new dashboard can be queried from the API
        endpoint = os.path.join(self.grafana.endpoints["dashboard_uid"], uid)
        headers = {"Content-Type": "application/json"}
        response = requests.get(url=endpoint,
                                headers=headers,
                                auth=("api_key", self.grafana.api_token))

        # Confirm uid and title are as expected
        self.assertEquals(response.json()["dashboard"]["uid"], uid)
        self.assertEquals(response.json()["dashboard"]["title"],
                          self.event_name)

    def test_create_grafana_dashboard_fail_authorization(self):
        self.grafana.api_token = "abcde"  # invalid API token

        expected_message = "Invalid API key"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard(self.event_name)

    def test_create_grafana_dashboard_fail_permissions(self):
        self.grafana.api_token = self.VIEWER  # API token with viewer permissions

        expected_message = "Access denied - check API permissions"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard(self.event_name)

    def test_create_grafana_dashboard_fail_empty_title(self):
        expected_message = "Dashboard title cannot be empty"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard("")

    def test_validate_credentials_success(self):
        # should return True if credentials are valid
        self.assertTrue(self.grafana.validate_credentials())

    def test_update_dashboard_title_success(self):
        event = self.create_venue_and_event(self.event_name)
        self.grafana.create_dashboard(self.event_name)
        new_name = event.name + " foo"
        self.assertTrue(self.grafana.update_dashboard_title(event, new_name))

    def test_update_dashboard_title_fail_same_name(self):
        event = self.create_venue_and_event(self.event_name)
        self.grafana.create_dashboard(self.event_name)
        self.assertFalse(self.grafana.update_dashboard_title(
            event, event.name))

    def test_update_dashboard_title_fail_no_dashboard(self):
        event = self.create_venue_and_event(self.event_name)
        self.assertFalse(self.grafana.update_dashboard_title(
            event, event.name))

    def test_validate_credentials_fail_authorization(self):
        self.grafana.api_token = "abcde"  # invalid API token

        expected_message = "Grafana API validation failed: Invalid API key"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.validate_credentials()

    def test_validate_credentials_fail_permissions(self):
        self.grafana.api_token = self.VIEWER  # API token with viewer permissions

        expected_message = ("Grafana API validation failed: Access denied - "
                            "check API permissions")
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.validate_credentials()

    def test_create_grafana_dashboard_fail_duplicate_title(self):
        # Should return a JSON object with a message that the dashboard already exists
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        expected_message = "Dashboard with the same name already exists"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard(self.event_name)

    def test_delete_grafana_dashboard(self):
        # Create a dashboard
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Delete the dashboard, should return True if deleted
        deleted_dashboard = self.grafana.delete_dashboard(dashboard["uid"])
        self.assertTrue(deleted_dashboard)

        # Confirm that dashboard was deleted by querying it
        endpoint = os.path.join(self.grafana.endpoints["dashboard_uid"],
                                dashboard["uid"])
        headers = {"Content-Type": "application/json"}
        response = requests.get(url=endpoint,
                                headers=headers,
                                auth=("api_key", self.grafana.api_token))
        self.assertTrue(response.json()["message"])
        self.assertEquals(response.json()["message"], "Dashboard not found")

    def test_delete_grafana_dashboard_fail(self):
        # Generate a random uid
        uid = self.grafana.generate_random_string(10)

        # Attempt to delete dashboard that doesn't exist, should return False
        deleted_dashboard = self.grafana.delete_dashboard(uid)
        self.assertFalse(deleted_dashboard)

    def test_add_panel(self):
        # Create a dashboard, confirm it was created and retrieve its UID
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)
        uid = dashboard["uid"]

        # Create an event and venue
        event = self.create_venue_and_event(self.event_name)

        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()

        # Add a panel to the dashboard
        self.grafana.add_panel(sensor, event)

        # Retrieve the current dashboard
        dashboard_info = self.grafana.get_dashboard_with_uid(uid)

        # Confirm that a panel was added to the dashboard with the expected title
        self.assertTrue(dashboard_info)
        self.assertTrue(dashboard_info["dashboard"])
        self.assertTrue(dashboard_info["dashboard"]["panels"])
        self.assertTrue(len(dashboard_info["dashboard"]["panels"]) == 1)
        self.assertTrue(
            dashboard_info["dashboard"]["panels"][0]["title"] == sensor.name)

    def test_add_multiple_panels(self):
        # Create a dashboard, confirm it was created and retrieve its UID
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)
        uid = dashboard["uid"]

        # Create an event and venue
        event = self.create_venue_and_event(self.event_name)

        # Create a sensor type
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        # Create 10 sensors, invoking add_panel for each new sensor
        for i in range(10):
            sensor = AGSensor.objects.create(name="".join(
                [self.test_sensor_name, str(i)]),
                                             type_id=sensor_type)
            sensor.save()

            self.grafana.add_panel(sensor, event)

        # Query dashboard to confirm 10 panels were added
        dashboard_info = self.grafana.get_dashboard_with_uid(uid)
        self.assertTrue(dashboard_info)
        self.assertTrue(dashboard_info["dashboard"])
        self.assertTrue(dashboard_info["dashboard"]["panels"])
        self.assertTrue(len(dashboard_info["dashboard"]["panels"]) == 10)

        # Confirm correct title for each panel
        for i in range(10):
            name = "".join([self.test_sensor_name, str(i)])
            self.assertTrue(
                dashboard_info["dashboard"]["panels"][i]["title"] == name)

    def test_add_sensor_creates_panel_in_dashboard(self):
        # Create a dashboard, confirm it was created and retrieve its UID
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Create an event
        event = self.create_venue_and_event(self.event_name)

        # Make the event the active event
        AGActiveEvent.objects.create(agevent=event).save()

        # Create a sensor through the UI
        self.client.post(
            reverse(self.sensor_url),
            data={
                "submit_new_sensor": "",
                "sensor-name": self.test_sensor["name"],
                "field-names": self.test_sensor["field-names"],
                "data-types": self.test_sensor["data-types"],
                "units": self.test_sensor["units"],
                "sensor-graph-type": self.test_sensor_graph_type,
            },
        )

        self.assertEquals(AGSensor.objects.count(), 1)

        # Fetch the dashboard again
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        self.assertTrue(dashboard)

        # Confirm that a panel was added to the dashboard with the expected title
        self.assertTrue(dashboard)
        self.assertTrue(dashboard["dashboard"])
        self.assertTrue(dashboard["dashboard"]["panels"])
        self.assertTrue(len(dashboard["dashboard"]["panels"]) == 1)

        # Note: converting test_sensor_name to lowercase because currently
        # sensor names are automatically capitalized when they are created
        self.assertEquals(
            dashboard["dashboard"]["panels"][0]["title"],
            self.test_sensor["name"].lower(),
        )

    def test_update_sensor_name_updates_panel_title(self):
        # Create a dashboard
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Create an event, create a sensor
        event = self.create_venue_and_event(self.event_name)

        # Create AGSensorType object for foreign key reference
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor["name"].lower(),
            processing_formula=0,
            format={
                self.field_name_1: {
                    "data_type": self.data_type_1,
                    "unit": self.unit_1
                },
                self.field_name_2: {
                    "data_type": self.data_type_2,
                    "unit": self.unit_2
                },
            },
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        # Create AG Sensor Object
        sensor = AGSensor.objects.create(name=self.test_sensor["name"].lower(),
                                         type_id=sensor_type)
        sensor.save()

        # Add the sensor panel to the dashboard
        self.grafana.add_panel(sensor, event)

        updated_test_sensor_name = "foo"

        # Post Edited Name
        self.client.post(
            reverse(self.sensor_url),
            data={
                "edit_sensor": "",
                "sensor-name": self.test_sensor["name"].lower(),
                "sensor-name-updated": updated_test_sensor_name,
                "field-names": self.test_sensor["field-names"],
                "data-types": self.test_sensor["data-types"],
                "units": self.test_sensor["units"],
                "sensor-graph-type": self.test_sensor_graph_type,
            },
        )

        # Check that AGSensor object has new name
        sensor = AGSensor.objects.all()[0]
        self.assertEqual(sensor.name, updated_test_sensor_name)

        # Check that sensor name was updated
        sensor = AGSensor.objects.all()[0]
        self.assertEquals(sensor.name, updated_test_sensor_name)

        # Confirm that Grafana panel title was updated
        # Confirm that a panel was added to the dashboard with the expected title
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertTrue(dashboard)
        self.assertTrue(dashboard["dashboard"])
        self.assertTrue(dashboard["dashboard"]["panels"])
        self.assertTrue(len(dashboard["dashboard"]["panels"]) == 1)

        self.assertEquals(
            dashboard["dashboard"]["panels"][0]["title"],
            updated_test_sensor_name,
        )

    def test_update_sensor_type_updates_panel_query(self):
        # Create a dashboard, confirm it was created
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Create an event
        event = self.create_venue_and_event(self.event_name)

        # Create sensor and sensor type
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_name.lower(),
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        sensor = AGSensor.objects.create(name=self.test_sensor_name.lower(),
                                         type_id=sensor_type)
        sensor.save()

        # Create grafana sensor panel
        self.grafana.add_panel(sensor, event)

        # New fields
        field_names_updated = ["foo"]
        data_types_updated = ["float"]
        units_updated = ["km/h"]

        # Post edited sensor type
        self.client.post(
            reverse(self.sensor_url),
            data={
                "edit_sensor": "",
                "sensor-name": self.test_sensor["name"].lower(),
                "sensor-name-updated": self.test_sensor["name"].lower(),
                "field-names": field_names_updated,
                "data-types": data_types_updated,
                "units": units_updated,
                "sensor-graph-type": self.test_sensor_graph_type,
            },
        )

        # Confirm that a new Grafana panel was created with a query containing all of
        # the current field names (not checking the full syntax of the query, just that
        # the new field name is in the query)
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertIn(
            field_names_updated[0],
            dashboard["dashboard"]["panels"][0]["targets"][0]["rawSql"],
        )

    def test_update_sensor_name_and_type_updates_panel_title_and_query(self):
        # Create a dashboard, confirm it was created
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Create an event
        event = self.create_venue_and_event(self.event_name)

        # Create sensor and sensor type
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_name.lower(),
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        sensor = AGSensor.objects.create(name=self.test_sensor_name.lower(),
                                         type_id=sensor_type)
        sensor.save()

        # Create grafana sensor panel
        self.grafana.add_panel(sensor, event)

        # New fields
        updated_sensor_name = "bar"
        field_names_updated = ["foo"]
        data_types_updated = ["float"]
        units_updated = ["km/h"]

        # Post edited sensor type
        self.client.post(
            reverse(self.sensor_url),
            data={
                "edit_sensor": "",
                "sensor-name": self.test_sensor["name"].lower(),
                "sensor-name-updated": updated_sensor_name,
                "field-names": field_names_updated,
                "data-types": data_types_updated,
                "units": units_updated,
                "sensor-graph-type": self.test_sensor_graph_type,
            },
        )

        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        # Confirm title and query
        self.assertEquals(
            dashboard["dashboard"]["panels"][0]["title"],
            updated_sensor_name,
        )
        self.assertIn(
            field_names_updated[0],
            dashboard["dashboard"]["panels"][0]["targets"][0]["rawSql"],
        )

    def test_delete_sensor_deletes_panel_in_dashboard(self):
        # Create a dashboard, confirm it was created
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Create an event
        event = self.create_venue_and_event(self.event_name)

        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_name,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()

        self.grafana.add_panel(sensor, event)

        # Delete sensor
        self.client.post(
            reverse(self.delete_sensor_url,
                    kwargs={"sensor_name": sensor.name}),
            follow=True,
        )

        # Confirm sensor was deleted
        self.assertEquals(AGSensor.objects.count(), 0)

        # Confirm that sensor was deleted from Grafana
        # Fetch the dashboard again
        dashboard = self.grafana.get_dashboard_by_name(dashboard["slug"])
        self.assertTrue(dashboard)

        # Confirm that a panel was added to the dashboard with the expected title
        self.assertTrue(dashboard)
        self.assertTrue(dashboard["dashboard"])
        self.assertEquals(len(dashboard["dashboard"]["panels"]), 0)

    def test_add_panel_fail_no_dashboard_exists_for_event(self):
        # Create an event
        event = self.create_venue_and_event(self.event_name)

        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()

        # Check for expected ValueError and message
        expected_message = "Dashboard not found for this event."
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.add_panel(sensor, event)

    def test_create_postgres_datasource(self):
        # Create datasource
        self.grafana.create_postgres_datasource(self.datasource_name)

        # Query new datasource
        endpoint = os.path.join(self.grafana.endpoints["datasource_name"],
                                self.datasource_name)
        headers = {"Content-Type": "application/json"}
        response = requests.get(url=endpoint,
                                headers=headers,
                                auth=("api_key", self.grafana.api_token))

        # Confirm datasource exists with expected name
        self.assertEquals(response.json()["name"], self.datasource_name)

    def test_create_datasource_fail_authorization(self):
        self.grafana.api_token = "abcde"  # invalid API token

        expected_message = "Invalid API key"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_postgres_datasource(self.datasource_name)

    def test_create_datasource_fail_permissions(self):
        self.grafana.api_token = self.VIEWER  # API token with viewer permissions

        expected_message = "Access denied - check API permissions"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_postgres_datasource(self.datasource_name)

    def test_create_datasource_fail_empty_string(self):
        expected_message = "Response contains no message"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_postgres_datasource("")

    def test_delete_postgres_datasource(self):
        # create the datasource
        self.grafana.create_postgres_datasource(self.datasource_name)

        # deleted should be True if delete_datasource_by_name returns true
        deleted = self.grafana.delete_datasource_by_name(self.datasource_name)
        self.assertTrue(deleted)

        # confirm that the datasource was actually deleted by querying it
        endpoint = os.path.join(self.grafana.endpoints["datasource_name"],
                                self.datasource_name)
        headers = {"Content-Type": "application/json"}
        response = requests.get(url=endpoint,
                                headers=headers,
                                auth=("api_key", self.grafana.api_token))

        self.assertTrue(response.json()["message"])
        self.assertEquals(response.json()["message"], "Data source not found")

    def test_create_event_creates_dashboard_no_panels(self):
        self.grafana.create_postgres_datasource(self.datasource_name)

        # Create a venue
        venue = AGVenue.objects.create(
            name=self.event_name,
            description=self.test_venue_data["description"],
            latitude=self.test_venue_data["latitude"],
            longitude=self.test_venue_data["longitude"],
        )
        venue.save()

        # Send a request to create an event (should trigger the creation of a
        # grafana dashboard of the same name)
        self.client.post(
            reverse(self.event_url),
            data={
                "submit-event": "",
                "name": self.event_name,
                "date": self.test_event_data["date"],
                "description": self.test_event_data["description"],
                "venue_uuid": venue.uuid,
            },
        )

        # If there are spaces in the name, the GF API will replace them with dashes
        # to generate the "slug". A slug can be used to query the API.
        endpoint = os.path.join(
            self.grafana.hostname,
            "api/dashboards/db",
            self.event_name.lower().replace(" ", "-"),
        )

        response = requests.get(url=endpoint,
                                auth=("api_key", self.grafana.api_token))

        # Confirm that a dashboard was created with the expected name
        self.assertEquals(response.status_code, 200)
        self.assertEquals(response.json()["dashboard"]["title"],
                          self.event_name)

    def test_create_event_creates_dashboard_with_panel(self):
        self.grafana.create_postgres_datasource(self.datasource_name)

        # Create a venue
        venue = AGVenue.objects.create(
            name=self.test_venue_data["name"],
            description=self.test_venue_data["description"],
            latitude=self.test_venue_data["latitude"],
            longitude=self.test_venue_data["longitude"],
        )
        venue.save()

        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(name=self.test_sensor_name,
                                         type_id=sensor_type)
        sensor.save()

        # Send a request to create an event (should trigger the creation of a
        # grafana dashboard of the same name)
        self.client.post(
            reverse(self.event_url),
            data={
                "submit-event": "",
                "name": self.event_name,
                "date": self.test_event_data["date"],
                "description": self.test_event_data["description"],
                "venue_uuid": venue.uuid,
            },
        )

        # If there are spaces in the name, the GF API will replace them with dashes
        # to generate the "slug". A slug can be used to query the API.
        endpoint = os.path.join(
            self.grafana.hostname,
            "api/dashboards/db",
            self.event_name.lower().replace(" ", "-"),
        )

        response = requests.get(url=endpoint,
                                auth=("api_key", self.grafana.api_token))

        # Confirm that a dashboard was created with the expected name
        self.assertEquals(response.status_code, 200)
        self.assertEquals(response.json()["dashboard"]["title"],
                          self.event_name)

        # Confirm that a the new dashboard can be queried and has panel with expected
        # title
        dashboard_info = self.grafana.get_dashboard_with_uid(
            response.json()["dashboard"]["uid"])
        self.assertTrue(dashboard_info)
        self.assertTrue(dashboard_info["dashboard"])
        self.assertTrue(dashboard_info["dashboard"]["panels"])
        self.assertTrue(len(dashboard_info["dashboard"]["panels"]) == 1)
        self.assertTrue(
            dashboard_info["dashboard"]["panels"][0]["title"] == sensor.name)

    def test_delete_event_deletes_grafana_dashboard(self):
        self.grafana.create_postgres_datasource(self.datasource_name)

        # Create a venue
        venue = AGVenue.objects.create(
            name=self.event_name,
            description=self.test_venue_data["description"],
            latitude=self.test_venue_data["latitude"],
            longitude=self.test_venue_data["longitude"],
        )
        venue.save()

        # Send a request to create an event (should trigger the creation of a
        # grafana dashboard of the same name)
        self.client.post(
            reverse(self.event_url),
            data={
                "submit-event": "",
                "name": self.event_name,
                "date": self.test_event_data["date"],
                "description": self.test_event_data["description"],
                "venue_uuid": venue.uuid,
            },
        )

        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        self.assertTrue(dashboard)

        # Retrieve event object
        event = AGEvent.objects.all().first()

        # Delete the event by posting to the delete view
        self.client.post(
            reverse(self.event_delete_url, kwargs={"event_uuid": event.uuid}))
        # Try and retrieve the dashboard
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertFalse(dashboard)

    def test_update_event_name_updates_grafana_dashboard_name(self):
        self.grafana.create_postgres_datasource(self.datasource_name)

        event = self.create_venue_and_event(self.event_name)

        updated_event_name = self.updated_event_name

        venue = AGVenue.objects.first()

        # Send a request to create an event (should trigger the creation of a
        # grafana dashboard of the same name)
        self.client.post(
            reverse(self.event_url),
            data={
                "submit-event": "",
                "name": self.event_name,
                "date": self.test_event_data["date"],
                "description": self.test_event_data["description"],
                "venue_uuid": venue.uuid,
            },
        )

        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        self.assertTrue(dashboard)

        # Update the event name
        self.client.post(
            reverse(self.event_update_url, kwargs={"event_uuid": event.uuid}),
            data={
                "name": updated_event_name,
                "description": self.test_event_data["description"],
                "venue_uuid": venue.uuid,
            },
        )

        # Confirm that a dashboard exists with the new event name
        dashboard = self.grafana.get_dashboard_by_name(updated_event_name)
        self.assertTrue(dashboard)
        self.assertEquals(dashboard["dashboard"]["title"], updated_event_name)
    def post(self, request, *args, **kwargs):
        if "submit" not in request.POST:
            return

        DB = settings.DATABASES
        gf_host = request.POST.get("gf_host")
        gf_name = request.POST.get("gf_name")
        gf_username = request.POST.get("gf_username")
        gf_password = request.POST.get("gf_password")
        gf_token = request.POST.get("gf_token")

        # check whether gf_host already in use
        existing_host = GFConfig.objects.filter(gf_host=gf_host)
        if "update-config" not in request.POST and existing_host:
            messages.error(
                request, "Hostname {} already in use".format(gf_host),
            )
            return redirect("/gfconfig")

        configure_grafana_github_url = os.path.join(
            GITHUB_DOCS_ROOT, CONFIGURE_GRAFANA_HELP_DOC
        )

        # user providing username/pasword, generate API key automatically
        if not gf_token:
            auth_url = make_auth_url(gf_host, gf_username, gf_password)
            try:
                gf_token = Grafana.create_api_key(
                    auth_url, "mercury-auto-admin", "Admin"
                )
            except ValueError as error:
                messages.error(
                    request,
                    mark_safe(
                        "Failed to create API token: {}. If this "
                        "problem persists, please provide "
                        "an admin API key directly with the 'Use API "
                        "Key' option in the `Add Grafana Host` form. "
                        'See <a target="_blank" '
                        'href="{}">Configure Grafana: How to Create an '
                        "API Token </a> to learn how to create an "
                        "API "
                        "key.".format(
                            error,
                            configure_grafana_github_url
                            + "#c-how-to-create-an-api-token",
                        )
                    ),
                )
                return redirect("/gfconfig")

        # the user is submitting an update form with username/password
        if "update-config" in request.POST and gf_username and gf_password:
            existing_host.update(
                gf_username=gf_username, gf_password=gf_password, gf_token=gf_token
            )
            messages.success(request, "Updated Grafana host: {}".format(gf_host))
            return redirect("/gfconfig")

        # new gfconfig record
        config_data = GFConfig(
            gf_name=gf_name,
            gf_host=gf_host,
            gf_username=gf_username,
            gf_password=gf_password,
            gf_token=gf_token,
            gf_db_host=DB["default"]["HOST"] + ":" + str(DB["default"]["PORT"]),
            gf_db_name=DB["default"]["NAME"],
            gf_db_username=DB["default"]["USER"],
            gf_db_pw=DB["default"]["PASSWORD"],
        )

        # Create Grafana instance with host and token
        grafana = Grafana(config_data)

        try:
            grafana.validate_credentials()
        except ValueError as error:
            messages.error(request, error)
            return redirect("/gfconfig")

        # the user is submitting an update form with a validated API key
        if "update-config" in request.POST:
            existing_host.update(gf_token=gf_token)
            messages.success(request, "Updated Grafana host: {}".format(gf_host))
            return redirect("/gfconfig")

        try:
            grafana.create_postgres_datasource()
        except ValueError as error:
            messages.error(request, error)

        try:
            config_data.gf_current = True
            config_data.save()

            # If any events exist, add a dashboard for each event
            # If any sensors exist, add them to each event dashboard
            events = AGEvent.objects.all()
            sensors = AGSensor.objects.all()
            for event in events:
                grafana.create_dashboard(event.name)
                for sensor in sensors:
                    grafana.add_panel(sensor, event)

        except ValueError as error:
            messages.warning(request, f"Warning: {error}")

        messages.success(request, "Created Grafana Host: {}".format(gf_name))
        return redirect("/gfconfig")