def test_instances():

    users = [UserFactory(username='******'), UserFactory(username='******')]

    surfaces = [
        SurfaceFactory(creator=users[0]),
        SurfaceFactory(creator=users[0]),
    ]

    topographies = [Topography1DFactory(surface=surfaces[0])]

    impl = AnalysisFunctionImplementationFactory()
    TopographyAnalysisFactory(function=impl.function, subject=topographies[0])

    return users, surfaces, topographies
Esempio n. 2
0
def test_recalculate(client):

    user = UserFactory()
    surface = SurfaceFactory(creator=user)
    topo1 = Topography1DFactory(surface=surface)
    topo2 = Topography1DFactory(surface=surface)

    func = AnalysisFunctionFactory(name="test function")
    impl = AnalysisFunctionImplementationFactory(function=func)

    client.force_login(user)

    with transaction.atomic():
        # trigger "recalculate" for two topographies
        response = client.post(
            reverse('analysis:card-submit'), {
                'function_id': func.id,
                'subjects_ids_json': subjects_to_json([topo1, topo2]),
                'function_kwargs_json': '{}'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest')  # we need an AJAX request
        assert response.status_code == 200

    #
    # Analysis objects should be there and marked for the user
    #
    analysis1 = Analysis.objects.get(function=func, topography=topo1)
    analysis2 = Analysis.objects.get(function=func, topography=topo2)

    assert user in analysis1.users.all()
    assert user in analysis2.users.all()
def test_publication_link_in_xlsx_download(client, two_analyses_two_publications, handle_usage_statistics):
    (analysis1, analysis2, pub1, pub2) = two_analyses_two_publications

    #
    # Now two publications are involved in these analyses
    #
    download_url = reverse('analysis:download', kwargs=dict(ids=f"{analysis1.id},{analysis2.id}",
                                                            card_view_flavor='plot',
                                                            file_format='xlsx'))
    user = UserFactory(username='******')
    client.force_login(user)
    response = client.get(download_url)
    assert response.status_code == 200

    tmp = tempfile.NamedTemporaryFile(suffix='.xlsx')  # will be deleted automatically
    tmp.write(response.content)
    tmp.seek(0)

    xlsx = openpyxl.load_workbook(tmp.name)

    sheet = xlsx['INFORMATION']
    col_B = sheet['B']
    col_B_values = [str(c.value) for c in col_B]
    assert any(pub1.get_absolute_url() in v for v in col_B_values)
    assert any(pub2.get_absolute_url() in v for v in col_B_values)
def test_import_downloaded_surface_archive(client):

    username = '******'
    surface_name = "Test Surface for Import"
    surface_category = 'dum'
    user = UserFactory(username=username)
    surface = SurfaceFactory(creator=user,
                             name=surface_name,
                             category=surface_category)
    topo1 = Topography2DFactory(surface=surface,
                                name='2D Measurement',
                                size_x=10,
                                size_y=10,
                                unit='mm')
    topo2 = Topography1DFactory(surface=surface,
                                name='1D Measurement',
                                size_x=10,
                                unit='µm')

    client.force_login(user)

    download_url = reverse('manager:surface-download',
                           kwargs=dict(surface_id=surface.id))
    response = client.get(download_url)

    # write downloaded data to temporary file and open
    with tempfile.NamedTemporaryFile(mode='wb') as zip_archive:
        zip_archive.write(response.content)
        zip_archive.seek(0)

        # reimport the surface
        call_command('import_surfaces', username, zip_archive.name)

    surface_copy = Surface.objects.get(
        description__icontains='imported from file')

    #
    # Check surface
    #
    assert surface_copy.name == surface.name
    assert surface_copy.category == surface.category
    assert surface.description in surface_copy.description
    assert surface_copy.tags == surface.tags

    #
    # Check imported topographies
    #
    assert surface_copy.num_topographies() == surface.num_topographies()

    for tc, t in zip(surface_copy.topography_set.order_by('name'),
                     surface.topography_set.order_by('name')):

        #
        # Compare individual topographies
        #
        for attrname in [
                'name', 'description', 'size_x', 'size_y', 'height_scale',
                'measurement_date', 'unit', 'creator', 'data_source', 'tags'
        ]:
            assert getattr(tc, attrname) == getattr(t, attrname)
Esempio n. 5
0
def test_go_link(client, example_pub):
    user = UserFactory()
    client.force_login(user)
    response = client.get(reverse(
        'publication:go', kwargs=dict(short_url=example_pub.short_url)),
                          follow=True)
    assert response.status_code == 200
    assert_in_content(response, example_pub.surface.name)
Esempio n. 6
0
def stats_instances(db):
    user_1 = UserFactory()
    user_2 = UserFactory()
    surf_1A = SurfaceFactory(creator=user_1)
    surf_1B = SurfaceFactory(creator=user_1)
    surf_2A = SurfaceFactory(creator=user_2)
    topo_1Aa = Topography1DFactory(surface=surf_1A)
    topo_1Ab = Topography1DFactory(surface=surf_1A)
    topo_1Ba = Topography1DFactory(surface=surf_1B)
    topo_2Aa = Topography1DFactory(surface=surf_2A)
    func = AnalysisFunctionFactory()
    AnalysisFunctionImplementationFactory(function=func)
    TopographyAnalysisFactory(subject=topo_1Aa, function=func)
    TopographyAnalysisFactory(subject=topo_1Ab, function=func)
    TopographyAnalysisFactory(subject=topo_2Aa, function=func)

    return user_1, user_2, surf_1A
def test_login_statistics(client):

    today = datetime.date.today()

    user1 = UserFactory()
    user2 = UserFactory()

    client.force_login(user1)
    client.logout()

    client.force_login(user2)
    client.logout()

    # Signal is not called for some reason - calling signal handler manually
    track_user_login(sender=user2.__class__)

    #
    # There should be two logins for two users now for today
    #
    m = Metric.objects.USERS_LOGIN_COUNT
    s = StatisticByDate.objects.get(metric=m, date=today)
    assert s.value == 2

    #
    # Check also for a specific day
    #
    yesterday = today - datetime.timedelta(1)

    with freeze_time(yesterday):
        client.force_login(user1)
        client.logout()
        track_user_login(sender=user1.__class__)

        #
        # There should be one login for one user for yesterday
        #
        s = StatisticByDate.objects.get(metric=m, date=yesterday)
        assert s.value == 1

        #
        # There should be still those logins from today
        #
        s = StatisticByDate.objects.get(metric=m, date=today)
        assert s.value == 2
def test_warnings_for_different_arguments(client, handle_usage_statistics):
    user = UserFactory()
    surf1 = SurfaceFactory(creator=user)
    surf2 = SurfaceFactory(creator=user)
    topo1a = Topography1DFactory(surface=surf1)
    topo1b = Topography1DFactory(surface=surf1)
    topo2a = Topography1DFactory(surface=surf2)

    func = AnalysisFunctionFactory()
    topo_impl = AnalysisFunctionImplementationFactory(function=func,
                                                      subject_type=topo1a.get_content_type(),
                                                      code_ref='topography_analysis_function_for_tests')
    surf_impl = AnalysisFunctionImplementationFactory(function=func,
                                                      subject_type=surf1.get_content_type(),
                                                      code_ref='surface_analysis_function_for_tests')

    #
    # Generate analyses for topographies with differing arguments
    #
    kwargs_1a = pickle.dumps(dict(a=1, b=2))
    kwargs_1b = pickle.dumps(dict(a=1, b=3))  # differing from kwargs_1a!
    ana1a = TopographyAnalysisFactory(subject=topo1a, function=func, kwargs=kwargs_1a)
    ana1b = TopographyAnalysisFactory(subject=topo1b, function=func, kwargs=kwargs_1b)
    ana2a = TopographyAnalysisFactory(subject=topo2a, function=func)  # default arguments

    #
    # Generate analyses for surfaces with differing arguments
    #
    kwargs_1 = pickle.dumps(dict(a=1, c=2))
    kwargs_2 = pickle.dumps(dict(a=1, c=3))  # differing from kwargs_1a!
    ana1 = SurfaceAnalysisFactory(subject=surf1, function=func, kwargs=kwargs_1)
    ana2 = SurfaceAnalysisFactory(subject=surf2, function=func, kwargs=kwargs_2)

    client.force_login(user)

    #
    # request card, there should be warnings, one for topographies and one for surfaces
    #
    response = client.post(reverse("analysis:card"),
                           data={
                               'subjects_ids_json': subjects_to_json([topo1a, topo1b, topo2a, surf1, surf2]),
                               'function_id': func.id,
                               'card_id': "card-1",
                               'template_flavor': 'list'
                           },
                           HTTP_X_REQUESTED_WITH='XMLHttpRequest',
                           follow=True)

    assert response.status_code == 200

    assert_in_content(response,
                      "Arguments for this analysis function differ among chosen "
                      "subjects of type 'manager | topography'")
    assert_in_content(response,
                      "Arguments for this analysis function differ among chosen "
                      "subjects of type 'manager | surface'")
Esempio n. 9
0
def test_request_analysis(mocker):
    """Make sure analysis objects were created with correct parameters"""

    user = UserFactory()

    af = AnalysisFunctionFactory.create(name="somefunc")
    afimpl = AnalysisFunctionImplementationFactory.create(function=af)

    m = mocker.patch('topobank.analysis.models.AnalysisFunctionImplementation.python_function',
                     new_callable=mocker.PropertyMock)
    m.return_value = lambda: lambda topography, a, b, bins=15, window='hann': None  # defining default parameters here

    # mocker.patch('topobank.analysis.models.Analysis.objects.create')
    # mocker.patch('django.db.models.QuerySet.delete') # we don't need to test with delete here
    mocker.patch('topobank.taskapp.tasks.perform_analysis.delay')  # we don't want to calculate anything

    topo = Topography1DFactory()

    # just an abbreviation
    def assert_correct_args(analysis, expected_kwargs):
        kwargs = pickle.loads(analysis.kwargs)
        assert expected_kwargs == kwargs
        assert user in analysis.users.all() # make sure the user has been set

    # test case 1
    analysis = request_analysis(user, af, topo, a=1, b=2)
    assert_correct_args(analysis,
                        dict(a=1,
                             b=2,
                             bins=15,
                             window='hann'))  # check default parameters in database


    # test case 2
    analysis = request_analysis(user, af, topo, 1, 2, bins=10)
    assert_correct_args(analysis,
                        dict(a=1,
                             b=2,
                             bins=10,
                             window='hann'))

    # test case 3
    analysis = request_analysis(user, af, topo, 2, 1, window='hamming', bins=5)
    assert_correct_args(analysis,
                        dict(a=2,
                             b=1,
                             bins=5,
                             window='hamming'))
Esempio n. 10
0
def test_card_templates_simple(client, mocker, handle_usage_statistics):
    """Check whether correct template is selected."""

    #
    # Create database objects
    #
    password = "******"
    user = UserFactory(password=password)
    func1 = AnalysisFunctionFactory(card_view_flavor='power spectrum')
    topo1 = Topography1DFactory()

    # An analysis function with card_view_flavor='power spectrum'
    # should use the template which is needed for PowerSpectrumCardView.
    #
    # For the "detail" mode, there is an own template for power spectrum,
    # which should be returned. The the "list" mode, there is no
    # special template. Therefore, since "PowerSpectrumCardView" is
    # derived from the "PlotCardView" so far, the resulting
    # template should be 'plot_card_list.html'.

    assert client.login(username=user.username, password=password)

    response = client.post(
        reverse('analysis:card'),
        data={
            'function_id': func1.id,
            'card_id': 'card',
            'template_flavor': 'list',
            'subjects_ids_json': subjects_to_json([topo1]),
        },
        HTTP_X_REQUESTED_WITH='XMLHttpRequest')  # we need an AJAX request

    assert response.template_name == ['analysis/plot_card_list.html']

    response = client.post(
        reverse('analysis:card'),
        data={
            'function_id': func1.id,
            'card_id': 'card',
            'template_flavor': 'detail',
            'subjects_ids_json': subjects_to_json([topo1]),
        },
        HTTP_X_REQUESTED_WITH='XMLHttpRequest')  # we need an AJAX request

    assert response.template_name == [
        'analysis/powerspectrum_card_detail.html'
    ]
Esempio n. 11
0
def example_pub():
    """Fixture returning a publication which can be used as test example"""

    user = UserFactory()

    authors = "Alice, Bob"
    publication_date = datetime.date(2020, 1, 1)
    description = "This is a nice surface for testing."
    name = "Diamond Structure"

    surface = SurfaceFactory(name=name, creator=user, description=description)
    surface.tags = ['diamond']

    with freeze_time(publication_date):
        pub = surface.publish('cc0-1.0', authors)

    return pub
def test_publication_link_in_txt_download(client, two_analyses_two_publications, handle_usage_statistics):
    (analysis1, analysis2, pub1, pub2) = two_analyses_two_publications

    #
    # Now two publications are involved in these analyses
    #
    download_url = reverse('analysis:download', kwargs=dict(ids=f"{analysis1.id},{analysis2.id}",
                                                            card_view_flavor='plot',
                                                            file_format='txt'))
    user = UserFactory(username='******')
    client.force_login(user)
    response = client.get(download_url)
    assert response.status_code == 200

    txt = response.content.decode()

    assert pub1.get_absolute_url() in txt
    assert pub2.get_absolute_url() in txt
def test_renewal_on_topography_detrend_mode_change(client, mocker, django_capture_on_commit_callbacks):
    """Check whether thumbnail is renewed if detrend mode changes for a topography
    """

    from ..models import Topography
    renew_squeezed_mock = mocker.patch('topobank.manager.views.renew_squeezed_datafile.si')
    renew_topo_analyses_mock = mocker.patch('topobank.manager.views.renew_analyses_related_to_topography.si')
    renew_topo_images_mock = mocker.patch('topobank.manager.views.renew_topography_images.si')

    user = UserFactory()
    surface = SurfaceFactory(creator=user)
    topo = Topography1DFactory(surface=surface, size_y=1, detrend_mode='center')

    client.force_login(user)

    with django_capture_on_commit_callbacks(execute=True) as callbacks:
        response = client.post(reverse('manager:topography-update', kwargs=dict(pk=topo.pk)),
                               data={
                                   'save-stay': 1,  # we want to save, but stay on page
                                   'surface': surface.pk,
                                   'data_source': 0,
                                   'name': topo.name,
                                   'measurement_date': topo.measurement_date,
                                   'description': "something",
                                   'size_x': 1,
                                   'size_y': 1,
                                   'unit': 'nm',
                                   'height_scale': 1,
                                   'detrend_mode': 'height',
                                   'instrument_type': Topography.INSTRUMENT_TYPE_UNDEFINED,
                                   'has_undefined_data': False,
                                   'fill_undefined_data_mode': Topography.FILL_UNDEFINED_DATA_MODE_NOFILLING,
                               }, follow=True)

    # we just check here that the form is filled completely, otherwise the thumbnail would not be recreated too
    assert_no_form_errors(response)
    assert response.status_code == 200
    assert len(callbacks) == 1  # single chain for squeezed file, thumbnail and for analyses
    assert renew_topo_images_mock.called
    assert renew_topo_analyses_mock.called
    assert renew_squeezed_mock.called  # was directly called, not as callback from commit
Esempio n. 14
0
def test_go_download_link(client, example_pub, handle_usage_statistics):
    user = UserFactory()
    client.force_login(user)
    response = client.get(reverse(
        'publication:go-download',
        kwargs=dict(short_url=example_pub.short_url)),
                          follow=True)
    assert response.status_code == 200

    surface = example_pub.surface

    # open zip file and look into meta file, there should be two surfaces and three topographies
    with zipfile.ZipFile(BytesIO(response.content)) as zf:
        meta_file = zf.open('meta.yml')
        meta = yaml.safe_load(meta_file)
        assert len(meta['surfaces']) == 1
        assert len(
            meta['surfaces'][0]['topographies']) == surface.num_topographies()
        assert meta['surfaces'][0]['name'] == surface.name

    assert_in_content(response, example_pub.surface.name)
Esempio n. 15
0
def test_card_templates_for_power_spectrum(client, mocker,
                                           handle_usage_statistics):
    #
    # Create database objects
    #
    password = "******"
    user = UserFactory(password=password)
    func1 = AnalysisFunctionFactory(card_view_flavor='power spectrum')
    topo1 = Topography1DFactory()

    assert client.login(username=user.username, password=password)

    response = client.post(
        reverse('analysis:card'),
        data={
            'function_id': func1.id,
            'card_id': 'card',
            'template_flavor': 'list',
            'subjects_ids_json': subjects_to_json([topo1]),
        },
        HTTP_X_REQUESTED_WITH='XMLHttpRequest')  # we need an AJAX request

    # we get the inherited "plot" template with "list" flavor, because power spectrum
    # hasn't got an own template with "list" flavor
    assert response.template_name == ['analysis/plot_card_list.html']

    response = client.post(
        reverse('analysis:card'),
        data={
            'function_id': func1.id,
            'card_id': 'card',
            'template_flavor': 'detail',
            'subjects_ids_json': subjects_to_json([topo1]),
        },
        HTTP_X_REQUESTED_WITH='XMLHttpRequest')  # we need an AJAX request

    # for the power spectrum detail card there should be an own template
    assert response.template_name == [
        'analysis/powerspectrum_card_detail.html'
    ]
Esempio n. 16
0
def test_unmark_other_analyses_during_request_analysis(mocker):
    """
    When requesting an analysis with new arguments, the old analyses should still exist
    (at the moment, maybe delete later analyses without user),
    but only the latest one should be marked as "used" by the user
    """
    user = UserFactory()

    m = mocker.patch('topobank.analysis.models.AnalysisFunctionImplementation.python_function',
                     new_callable=mocker.PropertyMock)
    m.return_value = lambda: lambda topography, a, b, bins=15, window='hann': None

    af = AnalysisFunctionFactory(name="somefunc")
    afim = AnalysisFunctionImplementationFactory(function=af)

    topo = Topography1DFactory()

    a1 = TopographyAnalysisFactory(subject=topo, function=af, kwargs=pickle.dumps(dict(a=9, b=19)), users=[])
    a2 = TopographyAnalysisFactory(subject=topo, function=af, kwargs=pickle.dumps(dict(a=29, b=39)), users=[user])

    a3 = request_analysis(user, af, topo, a=1, b=2)

    #
    # Now there are three analyses for af+topo
    #
    assert Analysis.objects.filter(topography=topo, function=af).count() == 3

    #
    # Only one analysis is marked for user 'user'
    #
    analyses = Analysis.objects.filter(topography=topo, function=af, users__in=[user])

    assert len(analyses) == 1

    assert a1 not in analyses
    assert a2 not in analyses
    assert a3 in analyses

    assert pickle.loads(analyses[0].kwargs) == dict(a=1, b=2, bins=15, window='hann')
Esempio n. 17
0
def test_sheets(client, handle_usage_statistics):

    user = UserFactory()
    surface = SurfaceFactory(creator=user)
    topo1 = Topography2DFactory(surface=surface)
    topo2 = Topography2DFactory(surface=surface)

    save_landing_page_statistics(
    )  # save current state for users, surfaces, ..

    client.force_login(user)  # not counted as login here

    client.get(reverse('manager:surface-detail', kwargs=dict(pk=surface.pk)))
    # Now there is one surface view

    # tried to use freezegun.freeze_time here,
    # but openpyxl had problems with FakeDate class
    call_command('export_usage_statistics')

    df = pd.read_excel("usage_statistics.xlsx",
                       sheet_name="summary",
                       engine='openpyxl')
    assert df.columns[0] == 'month'
    assert df.iloc[0, 1:].tolist() == [0, 1, 0, 0, 1, 1, 2, 0]
Esempio n. 18
0
def test_plot_card_if_no_successful_topo_analysis(client,
                                                  handle_usage_statistics):
    #
    # Create database objects
    #
    password = "******"
    user = UserFactory(password=password)
    topography_ct = ContentType.objects.get_for_model(Topography)
    surface_ct = ContentType.objects.get_for_model(Surface)
    func1 = AnalysisFunctionFactory(card_view_flavor='power spectrum')
    AnalysisFunctionImplementationFactory(function=func1,
                                          subject_type=topography_ct)
    AnalysisFunctionImplementationFactory(function=func1,
                                          subject_type=surface_ct)

    surf = SurfaceFactory(creator=user)
    topo = Topography1DFactory(surface=surf)  # also generates the surface

    # There is a successful surface analysis, but no successful topography analysis
    SurfaceAnalysisFactory(task_state='su',
                           subject_id=topo.surface.id,
                           subject_type_id=surface_ct.id,
                           function=func1,
                           users=[user])

    # add a failed analysis for the topography
    TopographyAnalysisFactory(task_state='fa',
                              subject_id=topo.id,
                              subject_type_id=topography_ct.id,
                              function=func1,
                              users=[user])

    assert Analysis.objects.filter(function=func1,
                                   subject_id=topo.id,
                                   subject_type_id=topography_ct.id,
                                   task_state='su').count() == 0
    assert Analysis.objects.filter(function=func1,
                                   subject_id=topo.id,
                                   subject_type_id=topography_ct.id,
                                   task_state='fa').count() == 1
    assert Analysis.objects.filter(function=func1,
                                   subject_id=topo.surface.id,
                                   subject_type_id=surface_ct.id,
                                   task_state='su').count() == 1

    # login and request plot card view
    assert client.login(username=user.username, password=password)

    response = client.post(
        reverse('analysis:card'),
        data={
            'function_id': func1.id,
            'card_id': 'card',
            'template_flavor': 'list',
            'subjects_ids_json':
            subjects_to_json([topo, topo.surface
                              ]),  # also request results for surface here
        },
        HTTP_X_REQUESTED_WITH='XMLHttpRequest')  # we need an AJAX request

    # should return without errors
    assert response.status_code == 200
Esempio n. 19
0
def user_bob():
    return UserFactory(username='******', password=PASSWORD, name='Bob Marley')
Esempio n. 20
0
def user_alice():
    return UserFactory(username='******',
                       password=PASSWORD,
                       name='Alice Wonderland')
Esempio n. 21
0
def test_latest_analyses_if_no_analyses():
    user = UserFactory()
    function = AnalysisFunctionFactory()
    assert get_latest_analyses(user, function, []).count() == 0
def test_view_shared_analysis_results(client, handle_usage_statistics):
    password = '******'

    #
    # create database objects
    #
    user1 = UserFactory(password=password)
    user2 = UserFactory(password=password)

    surface1 = SurfaceFactory(creator=user1)
    surface2 = SurfaceFactory(creator=user2)

    # create topographies + functions + analyses
    func1 = AnalysisFunctionFactory()
    impl1 = AnalysisFunctionImplementationFactory(function=func1)
    # func2 = AnalysisFunctionFactory()

    # Two topographies for surface1
    topo1a = Topography1DFactory(surface=surface1, name='topo1a')
    topo1b = Topography1DFactory(surface=surface1, name='topo1b')

    # One topography for surface2
    topo2a = Topography1DFactory(surface=surface2, name='topo2a')

    # analyses, differentiate by start time
    analysis1a_1 = TopographyAnalysisFactory(subject=topo1a, function=func1,
                                             start_time=datetime.datetime(2019, 1, 1, 12))
    analysis1b_1 = TopographyAnalysisFactory(subject=topo1b, function=func1,
                                             start_time=datetime.datetime(2019, 1, 1, 13))
    analysis2a_1 = TopographyAnalysisFactory(subject=topo2a, function=func1,
                                             start_time=datetime.datetime(2019, 1, 1, 14))

    # Function should have three analyses, all successful (the default when using the factory)
    assert func1.analysis_set.count() == 3
    assert all(a.task_state == 'su' for a in func1.analysis_set.all())

    # user2 shares surfaces, so user 1 should see surface1+surface2
    surface2.share(user1)

    #
    # Now we change to the analysis card view and look what we get
    #
    assert client.login(username=user1.username, password=password)

    response = client.post(reverse("analysis:card"),
                           data={
                               'subjects_ids_json': subjects_to_json([topo1a, topo1b, topo2a]),
                               'function_id': func1.id,
                               'card_id': 1,
                               'template_flavor': 'list'
                           },
                           HTTP_X_REQUESTED_WITH='XMLHttpRequest',
                           follow=True)

    # Function should still have three analyses, all successful (the default when using the factory)
    assert func1.analysis_set.count() == 3
    assert all(a.task_state == 'su' for a in func1.analysis_set.all())

    assert response.status_code == 200

    # We should see start times of all three topographies
    assert_in_content(response, '2019-01-01 12:00:00')  # topo1a
    assert_in_content(response, '2019-01-01 13:00:00')  # topo1b
    assert_in_content(response, '2019-01-01 14:00:00')  # topo2a

    client.logout()

    #
    # user 2 cannot access results from topo1, it is not shared
    #
    assert client.login(username=user2.username, password=password)

    response = client.post(reverse("analysis:card"),
                           data={
                               'subjects_ids_json': subjects_to_json([topo1a, topo1b, topo2a]),
                               'function_id': func1.id,
                               'card_id': 1,
                               'template_flavor': 'list'
                           },
                           HTTP_X_REQUESTED_WITH='XMLHttpRequest',
                           follow=True)

    assert response.status_code == 200

    assert_not_in_content(response, '2019-01-01 12:00:00')  # topo1a
    assert_not_in_content(response, '2019-01-01 13:00:00')  # topo1b
    assert_in_content(response, '2019-01-01 14:00:00')  # topo2a

    client.logout()