コード例 #1
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()
コード例 #2
0
def items_for_filtering(db, user_alice, user_bob):

    tag1 = TagModelFactory(name='tag1')
    tag2 = TagModelFactory(name='tag2')

    surface1 = SurfaceFactory(name='surface1',
                              creator=user_alice,
                              category='exp',
                              description='apple')
    topo1a = Topography1DFactory(name='topo1a', surface=surface1)
    topo1b = Topography1DFactory(name='topo1b',
                                 surface=surface1,
                                 tags=[tag1, tag2])

    surface2 = SurfaceFactory(name='surface2',
                              creator=user_alice,
                              category='dum',
                              tags=[tag1],
                              description='banana')
    topo2a = Topography1DFactory(name='topo2a', surface=surface2)

    surface3 = SurfaceFactory(name='surface3',
                              creator=user_bob,
                              category='sim',
                              description='cherry')
    topo3a = Topography1DFactory(name='topo3a', surface=surface3)

    surface3.share(user_alice)

    return dict(tags=[tag1, tag2],
                surfaces=[surface1, surface2, surface3],
                topographies=[topo1a, topo1b, topo2a, topo3a])
コード例 #3
0
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'")
コード例 #4
0
def test_increase_statistics_by_date_and_object(handle_usage_statistics):

    from trackstats.models import Metric, Domain, StatisticByDateAndObject

    Domain.objects.TESTDOMAIN = Domain.objects.register(ref='test',
                                                        name='A test domain')
    Metric.objects.TESTMETRIC = Metric.objects.register(
        ref='test', name='A test metric', domain=Domain.objects.TESTDOMAIN)

    metric = Metric.objects.TESTMETRIC

    topo1 = Topography1DFactory()
    topo2 = Topography1DFactory()

    today = datetime.date.today()

    #
    # Increase counter for topo1
    #
    increase_statistics_by_date_and_object(metric, obj=topo1, increment=2)

    s = StatisticByDateAndObject.objects.get(metric=metric, object_id=topo1.id)
    assert s.value == 2
    assert s.date == today

    increase_statistics_by_date_and_object(metric, obj=topo1)

    s = StatisticByDateAndObject.objects.get(metric=metric, object_id=topo1.id)
    assert s.value == 3
    assert s.date == today

    #
    # Increase counter for topo2
    #
    increase_statistics_by_date_and_object(metric, obj=topo2)
    s = StatisticByDateAndObject.objects.get(metric=metric, object_id=topo1.id)
    assert s.value == 3
    assert s.date == today
    s = StatisticByDateAndObject.objects.get(metric=metric, object_id=topo2.id)
    assert s.value == 1
    assert s.date == today

    #
    # Fake that a value was increased a day before
    #
    yesterday = today - datetime.timedelta(1)
    with freeze_time(yesterday):
        increase_statistics_by_date_and_object(metric, obj=topo2)
        s = StatisticByDateAndObject.objects.get(metric=metric,
                                                 object_id=topo2.id,
                                                 date=yesterday)
        assert s.value == 1
        assert s.date == yesterday
コード例 #5
0
def two_analyses_two_publications():
    surface1 = SurfaceFactory()
    Topography1DFactory(surface=surface1)
    surface2 = SurfaceFactory()
    Topography1DFactory(surface=surface2)
    pub1 = surface1.publish('cc0-1.0', surface1.creator.name)
    pub2 = surface2.publish('cc0-1.0', surface1.creator.name + ", " + surface2.creator.name)
    pub_topo1 = pub1.surface.topography_set.first()
    pub_topo2 = pub2.surface.topography_set.first()

    func = AnalysisFunctionFactory()
    AnalysisFunctionImplementationFactory(function=func)
    analysis1 = TopographyAnalysisFactory(subject=pub_topo1, function=func)
    analysis2 = TopographyAnalysisFactory(subject=pub_topo2, function=func)

    return analysis1, analysis2, pub1, pub2
コード例 #6
0
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)
コード例 #7
0
def test_topography_as_analysis_subject():
    topo = Topography1DFactory()
    # we must have an implementation before creating the analysis
    impl = AnalysisFunctionImplementationFactory(
        code_ref='topography_analysis_function_for_tests',
        subject_type=ContentType.objects.get_for_model(topo))
    analysis = TopographyAnalysisFactory(subject=topo, function=impl.function)
    assert analysis.subject == topo
コード例 #8
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
コード例 #9
0
def items_for_selection(db, user_alice):

    tag1 = TagModelFactory(name="tag1")
    tag2 = TagModelFactory(name="tag2")

    surface1 = SurfaceFactory(creator=user_alice, name='surface1')
    topo1a = Topography1DFactory(surface=surface1, name='topo1a')
    topo1b = Topography1DFactory(surface=surface1,
                                 tags=[tag1, tag2],
                                 name='topo1b')

    surface2 = SurfaceFactory(creator=user_alice, tags=[tag1], name="surface2")
    topo2a = Topography1DFactory(surface=surface2, name='topo2a')

    return dict(tags=[tag1, tag2],
                surfaces=[surface1, surface2],
                topographies=[topo1a, topo1b, topo2a])
コード例 #10
0
def test_analysis_function_implementation():
    impl = AnalysisFunctionImplementationFactory()
    from ..functions import topography_analysis_function_for_tests

    assert impl.python_function() == topography_analysis_function_for_tests
    assert impl.get_default_kwargs() == dict(a=1, b="foo")

    t = Topography1DFactory()
    result = impl.eval(t, a=2, b="bar")
    assert result['comment'] == f"a is 2 and b is bar"
コード例 #11
0
def test_analysis_function():
    func = AnalysisFunctionFactory()
    impl = AnalysisFunctionImplementationFactory(function=func)
    from ..functions import topography_analysis_function_for_tests

    ct = ContentType.objects.get_for_model(Topography)
    assert func.python_function(ct) == topography_analysis_function_for_tests
    assert func.get_default_kwargs(ct) == dict(a=1, b="foo")

    t = Topography1DFactory()
    result = func.eval(t, a=2, b="bar")
    assert result['comment'] == f"a is 2 and b is bar"
コード例 #12
0
def test_exception_implementation_missing():
    # We create an implementation for surfaces, but not for topographies
    topo = Topography1DFactory()
    surface = topo.surface

    impl = AnalysisFunctionImplementationFactory(
        code_ref='surface_analysis_function_for_tests',
        subject_type=ContentType.objects.get_for_model(surface))
    function = impl.function

    function.eval(surface)  # that's okay, it's implemented
    with pytest.raises(ImplementationMissingException):
        function.eval(topo)  # that's not implemented
コード例 #13
0
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
コード例 #14
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'))
コード例 #15
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'
    ]
コード例 #16
0
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
コード例 #17
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'
    ]
コード例 #18
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')
コード例 #19
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
コード例 #20
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()