Пример #1
0
def _init(import_demo_activities=False):
    _build_home()
    if Path(WORKOUTIZER_DB_PATH).is_file():
        execute_from_command_line(["manage.py", "check"])
        from wizer import models

        try:
            if models.Settings.objects.count() == 1:
                if models.Activity.objects.count() == 0 and import_demo_activities:
                    click.echo("Found initialized db, but with no demo activity, importing...")
                else:
                    click.echo(
                        f"Found initialized db at {WORKOUTIZER_DB_PATH}, maybe you want to run wkz \n"
                        "instead. If you really want to initialize wkz consider removing the existing db file. \n"
                        "Aborting."
                    )
                    return
        except OperationalError:
            pass  # means required tables are not set up - continuing with applying migrations
    execute_from_command_line(["manage.py", "collectstatic", "--noinput"])
    execute_from_command_line(["manage.py", "migrate"])
    from wizer import models

    # insert settings
    models.get_settings()
    _check()

    if import_demo_activities:
        # import demo activities
        from wizer.file_importer import import_activity_files, prepare_import_of_demo_activities

        prepare_import_of_demo_activities(models)
        import_activity_files(models, importing_demo_data=True)
        click.echo(f"Database and track files are stored in: {WORKOUTIZER_DIR}")
Пример #2
0
def test_settings_page__no_demo_activity(live_server, webdriver):
    models.get_settings()
    webdriver.get(live_server.url + reverse("settings"))

    assert webdriver.find_element_by_tag_name("h3").text == "Settings"

    headings = [h.text for h in webdriver.find_elements_by_tag_name("h5")]
    assert "File Importer" in headings
    assert "Reimporter" in headings

    # verify the text of the input field labels
    input_labels = [
        link.text for link in webdriver.find_elements_by_class_name("col-sm-4")
    ]
    assert "Path to Traces Directory:" in input_labels
    input_labels.remove("Path to Traces Directory:")
    assert "Path to Garmin Device:" in input_labels
    input_labels.remove("Path to Garmin Device:")
    assert "Delete fit Files after Copying:" in input_labels
    input_labels.remove("Delete fit Files after Copying:")
    assert "Reimport all Files:" in input_labels
    input_labels.remove("Reimport all Files:")
    # verify that the list is empty after remove all given input labels
    assert len(input_labels) == 0

    # verify no demo activity is present
    assert len(models.Activity.objects.filter(is_demo_activity=True)) == 0
    # no Demo heading present
    assert "Demo" not in headings
Пример #3
0
def test_settings_page__demo_activity_present__delete_it(
        import_demo_data, live_server, webdriver):
    models.get_settings()
    webdriver.get(live_server.url + reverse("settings"))

    assert webdriver.find_element_by_tag_name("h3").text == "Settings"

    headings = [h.text for h in webdriver.find_elements_by_tag_name("h5")]
    assert "File Importer" in headings
    assert "Reimporter" in headings

    # verify no demo activity is present
    assert len(models.Activity.objects.filter(is_demo_activity=True)) == 19
    # Demo heading is present
    assert "Demo" in headings

    # also delete demo activity button is present
    first_delete_button = webdriver.find_element_by_id("delete-demo-data")
    assert first_delete_button.text == "  Delete"

    # click button to verify demo data gets deleted
    first_delete_button.click()
    assert webdriver.current_url == live_server.url + reverse(
        "delete-demo-data")

    # on the new page find the additional delete button and click it
    second_delete_button = webdriver.find_element_by_class_name("btn-space")
    assert second_delete_button.text == "  Delete"
    second_delete_button.click()

    # verify that all demo data got deleted
    assert len(models.Activity.objects.filter(is_demo_activity=True)) == 0
Пример #4
0
def import_one_activity(db, tracks_in_tmpdir):
    models.get_settings()
    assert models.Settings.objects.count() == 1

    def _copy_activity(file_name: str):
        copy_demo_fit_files_to_track_dir(
            source_dir=django_settings.INITIAL_TRACE_DATA_DIR,
            targe_dir=models.get_settings().path_to_trace_dir,
            list_of_files_to_copy=[file_name],
        )
        import_activity_files(models, importing_demo_data=False)
        assert models.Activity.objects.count() == 1

    return _copy_activity
Пример #5
0
def test_settings_page__edit_and_submit_form(live_server, webdriver):
    # get settings and check that all values are at their default configuration
    settings = models.get_settings()
    assert settings.path_to_trace_dir == django_settings.TRACKS_DIR
    assert settings.path_to_garmin_device == "/run/user/1000/gvfs/"
    assert settings.delete_files_after_import is False
    assert settings.number_of_days == 30

    # go to settings page
    webdriver.get(live_server.url + reverse("settings"))
    assert webdriver.find_element_by_tag_name("h3").text == "Settings"

    # modify values by inserting into input fields
    trace_dir_input_field = webdriver.find_element_by_css_selector(
        "#id_path_to_trace_dir")
    trace_dir_input_field.clear()
    trace_dir_input_field.send_keys("some/dummy/path")

    garmin_device_input_field = webdriver.find_element_by_css_selector(
        "#id_path_to_garmin_device")
    garmin_device_input_field.clear()
    garmin_device_input_field.send_keys("garmin/dummy/path")

    # got removed, should not be accessible
    with pytest.raises(NoSuchElementException):
        webdriver.find_element_by_css_selector("#id_reimporter_updates_all")

    delete_files_input_field = webdriver.find_element_by_css_selector(
        "#id_delete_files_after_import")
    delete_files_input_field.click()

    # verify that the number of days field is not present nor editable
    with pytest.raises(NoSuchElementException):
        webdriver.find_element_by_css_selector("#id_number_of_days")

    # find button and submit
    button = webdriver.find_element_by_id("button")
    button.click()

    # again get settings and check that the values are the once entered above
    settings = models.get_settings()
    assert settings.path_to_trace_dir == "some/dummy/path"
    assert settings.path_to_garmin_device == "garmin/dummy/path"
    assert settings.delete_files_after_import is True
    # number of days should not be changed
    assert settings.number_of_days == 30

    # this attribute got removed, so verifying that is does no longer exist
    with pytest.raises(AttributeError):
        assert settings.reimporter_updates_all is True
Пример #6
0
def test_fake_device(db, fake_device, device_dir, activity_dir, fit_file):
    # initialize fake device
    device = fake_device(activity_files=[fit_file])

    settings = models.get_settings()
    mount_path = Path(settings.path_to_garmin_device)
    # once fake device is initialized, mount path should be available
    assert mount_path.is_dir()

    # now mount the fake device, which should also contain a fit file
    device.mount()

    assert (mount_path / device_dir).is_dir()
    assert (mount_path / device_dir / activity_dir).is_dir()
    assert (mount_path / device_dir / activity_dir / fit_file).is_file()

    # check that mounting again does not have any effect
    device.mount()
    assert (mount_path / device_dir / activity_dir / fit_file).is_file()

    # unmount device again and verify dirs are gone
    device.unmount()
    assert not (mount_path / device_dir).is_dir()
    assert not (mount_path / device_dir / activity_dir / fit_file).is_file()

    # check that unmounting again does not have any effect
    device.unmount()
    assert not (mount_path / device_dir).is_dir()
    assert not (mount_path / device_dir / activity_dir / fit_file).is_file()

    # and again mounting should also work
    device.mount()
    assert (mount_path / device_dir / activity_dir / fit_file).is_file()
Пример #7
0
    def get(self, request, list_of_activities: list):
        self.settings = models.get_settings()
        setattr(self.settings, "trace_width", django_settings.trace_line_width)
        setattr(self.settings, "trace_opacity", django_settings.trace_line_opacity)
        self.number_of_days = self.settings.number_of_days
        self.days_choices = models.Settings.days_choices
        traces = []
        for activity in list_of_activities:
            if activity.trace_file:
                coordinates = json.dumps(
                    get_list_of_coordinates(
                        json.loads(activity.trace_file.longitude_list), json.loads(activity.trace_file.latitude_list)
                    )
                )
                sport = activity.sport.name
                if coordinates != "[]":
                    traces.append(GeoTrace(pk=activity.pk, name=activity.name, sport=sport, coordinates=coordinates))
        has_traces = True if traces else False

        if traces:
            traces, colors = cut_list_to_have_same_length(traces, lines_colors, mode="fill end", modify_only_list2=True)
            traces = zip(traces, colors)
        return {
            "traces": traces,
            "settings": self.settings,
            "days": self.number_of_days,
            "choices": self.days_choices,
            "has_traces": has_traces,
        }
Пример #8
0
def test__start_device_watchdog(transactional_db, fake_device, device_dir,
                                activity_dir, fit_file_a, fit_file_b):
    # initialize fake device with two fit files
    device = fake_device(activity_files=[fit_file_a, fit_file_b])

    settings = models.get_settings()
    mount_path = Path(settings.path_to_garmin_device)
    trace_dir = Path(settings.path_to_trace_dir)

    # verify mount path exists
    assert mount_path.is_dir()

    # ensure fit files are not already present in trace dir
    assert not (trace_dir / fit_file_a).is_file()
    assert not (trace_dir / fit_file_b).is_file()

    # start device watch dog
    _start_device_watchdog(mount_path, trace_dir,
                           settings.delete_files_after_import)

    # now mount device which contains fit files
    device.mount()

    # verify the fit files are present on the mounted device
    assert (mount_path / device_dir / activity_dir / fit_file_a).is_file()
    assert (mount_path / device_dir / activity_dir / fit_file_b).is_file()

    # verify that the fit files are now present in the trace dir
    assert condition((trace_dir / "garmin" / fit_file_a).is_file, operator.is_,
                     True)
    assert condition((trace_dir / "garmin" / fit_file_b).is_file, operator.is_,
                     True)
Пример #9
0
def test_mount_device__success(db, monkeypatch, tmpdir, client):
    # prepare settings
    target_dir = tmpdir.mkdir("tracks")
    settings = models.get_settings()
    settings.path_to_garmin_device = tmpdir  # source
    settings.path_to_trace_dir = target_dir  # target
    settings.save()

    def check_output(dummy):
        return "dummy\nstring\nsome\ncontent\ncontaining\nGarmin"

    monkeypatch.setattr(subprocess, "check_output", check_output)

    # mock output of subprocess to prevent function from failing
    def try_to_mount_device():
        return "dummy-string"

    monkeypatch.setattr(fit_collector, "try_to_mount_device",
                        try_to_mount_device)

    # mock output of actual mounting command
    def mount(bus, dev):
        return "Mounted"

    monkeypatch.setattr(fit_collector, "_mount_device_using_gio", mount)

    # create directory to import the fit files from
    fake_device_dir = os.path.join(tmpdir, "mtp:host/Primary/GARMIN/Activity/")
    os.makedirs(fake_device_dir)

    res = client.post("/mount-device/")
    assert res.status_code == 200
Пример #10
0
def fake_device(tmp_path, device_dir, activity_dir):
    settings = models.get_settings()

    mount_path = tmp_path / "mount_path"
    mount_path.mkdir()
    assert mount_path.is_dir()
    settings.path_to_garmin_device = mount_path

    trace_dir = tmp_path / "trace_dir"
    trace_dir.mkdir()
    assert trace_dir.is_dir()
    settings.path_to_trace_dir = trace_dir

    settings.save()

    def _get_device(activity_files: List[str],
                    device_dir: str = device_dir,
                    activity_dir: str = activity_dir):
        return FakeDevice(
            mount_path=settings.path_to_garmin_device,
            device_dir=device_dir,
            activity_dir=activity_dir,
            activity_files=activity_files,
        )

    return _get_device
Пример #11
0
def test__start_file_importer_watchdog__missing_dir(db, caplog):
    invalid_dir = "/some/random/non_existent/path/"

    settings = models.get_settings()
    settings.path_to_trace_dir = invalid_dir
    settings.save()

    _start_file_importer_watchdog(invalid_dir, models=models)
    assert f"Path to trace dir {invalid_dir} does not exist. File Importer watchdog is disabled." in caplog.text
Пример #12
0
def set_number_of_days(request, number_of_days):
    settings = models.get_settings()
    settings.number_of_days = number_of_days
    log.debug(f"number of days: {number_of_days}")
    settings.save()
    if request.META.get("HTTP_REFERER"):
        return redirect(request.META.get("HTTP_REFERER"))
    else:
        return HttpResponseRedirect(reverse("home"))
Пример #13
0
def test__start_device_watchdog__missing_dir(db, caplog):
    invalid_dir = "/some/random/non_existent/path/"

    settings = models.get_settings()
    settings.path_to_garmin_device = invalid_dir
    settings.save()

    _start_device_watchdog(invalid_dir, settings.path_to_trace_dir,
                           settings.delete_files_after_import)
    assert f"Device mount path {invalid_dir} does not exist. Device watchdog is disabled." in caplog.text
Пример #14
0
 def get(self, request, sports_name_slug):
     log.debug(f"got sports name: {sports_name_slug}")
     settings = models.get_settings()
     if sports_name_slug == "undefined":
         log.warning("could not find sport - redirecting to home")
         return HttpResponseRedirect(reverse("home"))
     sport = models.Sport.objects.get(slug=sports_name_slug)
     activities = self.get_activity_data_for_plots(sport_id=sport.id)
     context = {}
     sports = models.Sport.objects.all().order_by("name")
     summary = get_summary_of_all_activities(sport_slug=sports_name_slug)
     if activities:
         script_history, div_history = plot_history(
             activities=activities,
             sport_model=models.Sport,
             number_of_days=settings.number_of_days,
         )
         context["script_history"] = script_history
         context["div_history"] = div_history
         context["activities_selected_for_plot"] = True
     else:
         context["activities_selected_for_plot"] = False
     map_context = super(SportsView,
                         self).get(request=request,
                                   list_of_activities=activities)
     if sport.evaluates_for_awards:
         top_awards = get_flat_list_of_pks_of_activities_in_top_awards(
             configuration.rank_limit, sports_name_slug)
         context["top_awards"] = top_awards
     try:
         sport = model_to_dict(
             models.Sport.objects.get(slug=sports_name_slug))
         sport["slug"] = sports_name_slug
     except ObjectDoesNotExist:
         log.critical("this sport does not exist")
         raise Http404
     page = 0
     return render(
         request,
         self.template_name,
         {
             **map_context,
             **context,
             "current_page": page,
             "is_last_page": False,
             "sports": sports,
             "summary": summary,
             "sport": sport,
             "form_field_ids": get_all_form_field_ids(),
         },
     )
Пример #15
0
def test__start_file_importer_watchdog_basic(transactional_db, tmp_path,
                                             test_data_dir, demo_data_dir,
                                             fit_file_a):
    assert models.Activity.objects.count() == 0
    assert models.BestSection.objects.count() == 0

    # update path_to_trace_dir in db accordingly, since import_activity_files will read it from the db
    settings = models.get_settings()
    trace_dir = tmp_path / "trace_dir"
    trace_dir.mkdir()
    assert trace_dir.is_dir()
    settings.path_to_trace_dir = trace_dir
    settings.save()

    _start_file_importer_watchdog(trace_dir, models=models)

    # put an activity fit file into the watched dir
    copy_demo_fit_files_to_track_dir(
        source_dir=demo_data_dir,
        targe_dir=trace_dir,
        list_of_files_to_copy=[fit_file_a],
    )
    assert (Path(trace_dir) / fit_file_a).is_file()

    # watchdog should now have triggered the file imported and activity should be in db
    assert condition(models.Activity.objects.count, operator.eq, 1)
    assert condition(models.BestSection.objects.count, operator.gt, 0)
    bs1 = models.BestSection.objects.count()

    # now put a activity GPX file into the watched dir
    copy_demo_fit_files_to_track_dir(
        source_dir=test_data_dir,
        targe_dir=trace_dir,
        list_of_files_to_copy=["example.gpx"],
    )

    assert condition(models.Activity.objects.count, operator.eq, 2)
    assert condition(models.BestSection.objects.count, operator.gt, bs1)
    bs2 = models.BestSection.objects.count()

    # create a non fit/gpx file to verify it won't be imported
    dummy_file = trace_dir / "fake_activity.txt"
    dummy_file.write_text("not a real activity", encoding="utf-8")

    # check that the dummy file was actually created
    assert Path(dummy_file).is_file()

    # but assert that the number of activities and best sections did not increase
    assert condition(models.Activity.objects.count, operator.eq, 2)
    assert condition(models.BestSection.objects.count, operator.eq, bs2)
Пример #16
0
    def ready(self):
        # ensure to only run with 'manage.py runserver' and not in auto reload thread
        if _was_runserver_triggered(
                sys.argv) and os.environ.get("RUN_MAIN", None) != "true":
            log.info(
                f"using workoutizer home at {django_settings.WORKOUTIZER_DIR}")
            from wizer import models

            # start watchdog to monitor whether a new device was mounted
            settings = models.get_settings()
            path_to_garmin_device = settings.path_to_garmin_device
            _start_device_watchdog(path_to_garmin_device,
                                   settings.path_to_trace_dir,
                                   settings.delete_files_after_import)

            # start watchdog for new files being placed into the tracks directory
            _start_file_importer_watchdog(path=django_settings.TRACKS_DIR,
                                          models=models)
Пример #17
0
def test_device_and_file_importer_watchdog(transactional_db, tmpdir,
                                           test_data_dir, demo_data_dir,
                                           fake_device, device_dir,
                                           activity_dir, fit_file_a,
                                           fit_file_b):
    assert models.Activity.objects.count() == 0
    assert models.BestSection.objects.count() == 0

    device = fake_device(activity_files=[fit_file_a, fit_file_b])

    settings = models.get_settings()
    mount_path = Path(settings.path_to_garmin_device)
    trace_dir = Path(settings.path_to_trace_dir)

    # verify mount path exists
    assert mount_path.is_dir()

    # ensure fit files are not already present in trace dir
    assert not (trace_dir / fit_file_a).is_file()
    assert not (trace_dir / fit_file_b).is_file()

    # start watchdogs
    _start_device_watchdog(mount_path, trace_dir,
                           settings.delete_files_after_import)
    _start_file_importer_watchdog(trace_dir, models=models)

    # mounting the device will:
    #  1. trigger device watchdog to copy fit files to trace dir, what in turn will
    #  2. trigger file importer watchdog to import the fit files into workoutizer activities
    device.mount()

    # verify the fit files are present on the mounted device
    assert (mount_path / device_dir / activity_dir / fit_file_a).is_file()
    assert (mount_path / device_dir / activity_dir / fit_file_b).is_file()

    # verify that the fit files are now present in the trace dir
    assert condition((trace_dir / "garmin" / fit_file_a).is_file, operator.is_,
                     True)
    assert condition((trace_dir / "garmin" / fit_file_b).is_file, operator.is_,
                     True)

    # check that the activities got imported
    assert condition(models.Activity.objects.count, operator.eq, 2)
    assert condition(models.BestSection.objects.count, operator.gt, 2)
Пример #18
0
def plot_trend(activities, sport_model):
    number_of_days = models.get_settings().number_of_days

    df = pd.DataFrame.from_records(
        activities.values("sport_id", "duration", "date"))
    df["date"] = pd.to_datetime(df["date"])
    df = df.set_index("date")
    days = int(number_of_days / 5)
    freq = days if days > 1 else 1
    df = df.groupby([pd.Grouper(freq=f"{freq}D"), "sport_id"]).agg({
        "duration":
        np.sum
    }).reset_index()
    df = df.pivot(index="date", columns="sport_id",
                  values="duration").fillna("0")
    sports = sport_model.objects.exclude(name="unknown").order_by("id").values(
        "id", "name", "color")
    id_color_mapping = {}
    for sport in sports:
        id_color_mapping[sport["id"]] = sport["color"]
    df = df.rename(columns=id_color_mapping)

    p = figure(width=280,
               height=200,
               x_axis_type="datetime",
               y_axis_type="datetime")
    p.multi_line(xs=[df.index.values] * len(df.columns),
                 ys=[df[name].values for name in df],
                 line_color=df.columns,
                 line_width=2)

    # render zero hours properly
    p.yaxis.major_label_overrides = {0: "0h"}
    p.toolbar.logo = None
    p.toolbar_location = None
    p.background_fill_color = "whitesmoke"
    p.border_fill_color = "whitesmoke"
    p.tools = []

    script_trend, div_trend = components(p)

    return script_trend, div_trend
Пример #19
0
 def get(self, request):
     page = 0
     settings = models.get_settings()
     self.sports = models.Sport.objects.all().order_by("name")
     activities = self.get_activity_data_for_plots()
     summary = get_summary_of_all_activities()
     context = {
         "sports": self.sports,
         "current_page": page,
         "is_last_page": False,
         "days": self.number_of_days,
         "choices": self.days_choices,
         "summary": summary,
         "page": "dashboard",
         "form_field_ids": get_all_form_field_ids(),
     }
     if activities:
         script_history, div_history = plot_history(
             activities=activities, sport_model=models.Sport, number_of_days=settings.number_of_days
         )
         script_pc, div_pc = plot_pie_chart(activities=activities)
         script_trend, div_trend = plot_trend(activities=activities, sport_model=models.Sport)
         plotting_context = {
             "script_history": script_history,
             "div_history": div_history,
             "script_pc": script_pc,
             "div_pc": div_pc,
             "script_trend": script_trend,
             "div_trend": div_trend,
             "activities_selected_for_plot": True,
         }
         return render(request, self.template_name, {**context, **plotting_context})
     else:
         log.warning("no activities found...")
         context["activities_selected_for_plot"] = False
     return render(request, self.template_name, {**context})
Пример #20
0
def settings_view(request):
    sports = models.Sport.objects.all().order_by("name")
    settings = models.get_settings()
    activities = models.Activity.objects.filter(is_demo_activity=True).count()
    form = forms.EditSettingsForm(request.POST or None, instance=settings)
    if request.method == "POST":
        if form.is_valid():
            log.debug(f"got valid form: {form.cleaned_data}")
            form.save()
            messages.success(request, "Successfully saved Settings!")
            return HttpResponseRedirect(reverse("settings"))
        else:
            log.warning(f"form invalid: {form.errors}")
    return render(
        request,
        "lib/settings.html",
        {
            "sports": sports,
            "form": form,
            "settings": settings,
            "form_field_ids": get_all_form_field_ids(),
            "delete_demos": True if activities else False,
        },
    )
Пример #21
0
def tracks_in_tmpdir(tmpdir):
    target_dir = tmpdir.mkdir("tracks")
    settings = models.get_settings()
    settings.path_to_trace_dir = target_dir
    settings.save()
Пример #22
0
 def get_days_config(self):
     self.settings = models.get_settings()
     self.number_of_days = self.settings.number_of_days
     self.days_choices = models.Settings.days_choices