示例#1
0
def test_gsim_required_attrs_mappings_are_in_gsims():
    # now test that all keys are in Gsims required attrs:
    gsims_attrs = set()
    for gsim in aval_gsims():
        gsims_attrs |= set(OQ.required_attrs(gsim))
    # exlude some attributes not currently in any gsim but that we
    # be included in future OpenQuake releases:
    exclude = set(['strike'])
    for att in GSIM_REQUIRED_ATTRS:
        if att not in exclude:
            assert att in gsims_attrs
示例#2
0
    def test_gsims_service(
            self,
            # pytest fixtures:
            testdata,
            areequal,
            client):
        '''tests the gsims API service'''
        inputdic = testdata.readyaml(self.request_filename)
        resp2 = client.post(self.url,
                            data=inputdic,
                            content_type='application/json')
        resp1 = client.get(querystring(inputdic, baseurl=self.url))
        assert resp1.status_code == resp2.status_code == 200
        assert areequal(resp1.json(), resp2.json())

        expected_gsims = [
            k[0] for k in aval_gsims(asjsonlist=True)
            if k[2] == inputdic['trt']
        ]
        assert sorted(resp1.json()) == sorted(expected_gsims)

        # now try to supply two filters on gsim:
        expected_gsims = [_ for _ in expected_gsims if _[0] in ('A', )]
        inputdic['gsim'] = expected_gsims
        resp1 = client.get(self.url, data=inputdic)
        inputdic['gsim'] = 'A*'
        resp2 = client.post(self.url,
                            data=inputdic,
                            content_type='application/json')
        assert resp1.status_code == resp2.status_code == 200
        assert areequal(resp1.json(), resp2.json())
        assert sorted(resp1.json()) == sorted(resp1.json()) == \
            sorted(expected_gsims)

        # test text format:
        resp2 = client.post(self.url,
                            data=dict(inputdic, format='text'),
                            content_type='text/csv')
        assert resp2.status_code == 200
        assert areequal(resp1.json(),
                        [_.decode('utf8') for _ in resp2.content.split()])
示例#3
0
def test_db_0(django_db_setup):
    '''Tests the egsim db'''
    with pytest.raises(MultipleObjectsReturned):  # @UndefinedVariable
        Gsim.objects.get()  # pylint: disable=no-member

    # from oq-engine
    # (https://github.com/gem/oq-engine/tree/master/openquake/hazardlib/gsim)
    #
    # AbrahamsonEtAl2014: 'PGA', 'SA', 'PGV'
    # const.TRT.ACTIVE_SHALLOW_CRUST
    #
    # AbrahamsonEtAl2015SInter: 'PGA' 'SA'
    # const.TRT.SUBDUCTION_INTERFACE
    #
    # BommerEtAl2009RSD: 'RSD595', 'RSD575'
    # const.TRT.ACTIVE_SHALLOW_CRUST
    #
    # CauzziEtAl2014: 'PGA', 'SA', 'PGV'
    # const.TRT.ACTIVE_SHALLOW_CRUST

    # there are:
    # 4 gsims with trt1
    # 4 gsims with trt2
    # 4 gsims with imt1 and imt2
    # 2 gsims with imt1
    # 2 gims with no imt

    test1 = list(aval_gsims())
    assert len(test1) <= len(OQ.gsims())

    test2 = list(aval_gsims())
    assert len(test2) <= len(OQ.gsims())
    assert len(test1) == len(test2)

    test1 = list(aval_imts())
    assert len(test1) <= len(OQ.imts())

    test1 = list(aval_trts())
    assert len(test1) <= len(OQ.trts())

    test1 = list(gsim_names())
    assert len(test1) <= len(OQ.gsims())

    # Note that below we can supply each instance key (string, e.g.
    # 'PGA') or the entity itself. Same for trt

    test1 = set(gsim_names(imts=['PGA', 'SA']))
    expected = set(
        ['AbrahamsonEtAl2014', 'AbrahamsonEtAl2015SInter', 'CauzziEtAl2014'])
    assert expected & test1 == expected
    assert 'BommerEtAl2009RSD' not in test1

    imtobjs = Imt.objects  # pylint: disable=no-member
    PGA, SA = list(imtobjs.filter(key='PGA')), list(imtobjs.filter(key='SA'))
    assert len(PGA) == len(SA) == 1
    PGA, SA = PGA[0], SA[0]
    # try by querying via objects (same result as above):
    test2 = set(gsim_names(imts=[PGA, SA]))
    assert test1 == test2
    # try to get by id (same result as above):
    test2 = set(gsim_names(imts=[PGA.id, SA.id]))
    assert test1 == test2

    test1 = set(gsim_names(imts=['PGA', 'SA'], trts=['SUBDUCTION_INTERFACE']))
    assert 'AbrahamsonEtAl2015SInter' in test1
    not_expected = set(
        ['AbrahamsonEtAl2014', 'BommerEtAl2009RSD', 'CauzziEtAl2014'])
    assert not (not_expected & test1)

    test1 = set(
        gsim_names(gsims=[_ for _ in aval_gsims() if _[0] == 'C'],
                   imts=['PGA', 'SA'],
                   trts=['ACTIVE_SHALLOW_CRUST']))
    assert 'CauzziEtAl2014' in test1
    not_expected = set([
        'AbrahamsonEtAl2014', 'AbrahamsonEtAl2015SInter', 'BommerEtAl2009RSD'
    ])
    assert not (not_expected & test1)

    test1 = set(gsim_names(imts=['PGA', 'SA', 'PGV']))
    expected = set(
        ['AbrahamsonEtAl2014', 'AbrahamsonEtAl2015SInter', 'CauzziEtAl2014'])
    assert expected & test1 == expected
    assert 'BommerEtAl2009RSD' not in test1

    # BUT now provide strict match for imts:
    test1 = set(gsim_names(imts=['PGA', 'SA', 'PGV'], imts_match_all=True))
    expected = set(['AbrahamsonEtAl2014', 'CauzziEtAl2014'])
    assert expected & test1 == expected
    assert 'BommerEtAl2009RSD' not in test1
    assert 'AbrahamsonEtAl2015SInter' not in test1

    # AbrahamsonEtAl2014: 'PGA', 'SA', 'PGV'
    # const.TRT.ACTIVE_SHALLOW_CRUST
    #
    # AbrahamsonEtAl2015SInter: 'PGA' 'SA'
    # const.TRT.SUBDUCTION_INTERFACE
    #
    # BommerEtAl2009RSD: 'RSD595', 'RSD575'
    # const.TRT.ACTIVE_SHALLOW_CRUST
    #
    # CauzziEtAl2014: 'PGA', 'SA', 'PGV'
    # const.TRT.ACTIVE_SHALLOW_CRUST

    expected_imts = ['PGA', 'SA']
    assert sorted(
        shared_imts([
            'AbrahamsonEtAl2014', 'AbrahamsonEtAl2015SInter', 'CauzziEtAl2014'
        ])) == expected_imts

    expected_gsims = set(
        ['AbrahamsonEtAl2014', 'AbrahamsonEtAl2015SInter', 'CauzziEtAl2014'])
    sharing_gsims_set = set(sharing_gsims(['PGA', 'SA']))
    assert sharing_gsims_set & expected_gsims == expected_gsims
    assert 'BommerEtAl2009RSD' not in sharing_gsims_set

    # type a point on italy (active shallow crust), and see
    trsel = TrSelector('SHARE', lon0=11, lat0=44)
    test1 = set(gsim_names(tr_selector=trsel))
    expected_gsims = set(
        ['AbrahamsonEtAl2014', 'BommerEtAl2009RSD', 'CauzziEtAl2014'])
    assert expected_gsims & test1 == expected_gsims
    assert 'AbrahamsonEtAl2015SInter' not in test1
示例#4
0
def test_clear_db(django_db_setup):
    test1 = list(aval_gsims())
    assert len(test1)
    empty_all()
    test1 = list(aval_gsims())
    assert not len(test1)
示例#5
0
def main(request, selected_menu=None):
    '''view for the main page'''

    # Tab components (one per tab, one per activated vue component)
    # (key, label and icon) (the last is bootstrap fontawesome name)
    components_tabs = [(KEY.HOME, 'Home', 'fa-home'),
                       (KEY.GSIMS, 'Gsim selection', 'fa-map-marker'),
                       (KEY.TRELLIS, 'Trellis Plots', 'fa-area-chart'),
                       (KEY.GMDBPLOT, 'Ground Motion database', 'fa-database'),
                       (KEY.RESIDUALS, 'Residuals', 'fa-bar-chart'),
                       (KEY.TESTING, 'Testing', 'fa-list'),
                       (KEY.DOC, 'API Documentation', 'fa-info-circle')]
    # this can be changed if needed:
    sel_component = KEY.HOME if not selected_menu else selected_menu

    # properties to be passed to vuejs components.
    # If you change THE KEYS of the dict here you should change also the
    # javascript:
    components_props = {
        KEY.HOME: {
            'src': URLS.HOME_PAGE
        },
        KEY.GSIMS: {
            'form': GsimsView.formclass().to_rendering_dict(),
            'url': URLS.GSIMS_RESTAPI,
            'urls': {
                'getTectonicRegionalisations': URLS.GSIMS_TR
            }
        },
        KEY.TRELLIS: {
            'form': TrellisView.formclass().to_rendering_dict(),
            'url': URLS.TRELLIS_RESTAPI,
            'urls': {
                # download* below must be pairs of [key, url]. Each url
                # must return a
                # response['Content-Disposition'] = 'attachment; filename=%s'
                # url can also start with 'file:///' telling the frontend
                # to simply download the data
                'downloadRequest':
                [[
                    'json',
                    "%s/%s.request.json" %
                    (URLS.TRELLIS_DOWNLOAD_REQ, KEY.TRELLIS)
                ],
                 [
                     'yaml',
                     "%s/%s.request.yaml" %
                     (URLS.TRELLIS_DOWNLOAD_REQ, KEY.TRELLIS)
                 ]],
                'downloadResponse': [
                    ['json', 'file:///%s.json' % KEY.TRELLIS],
                    [
                        'text/csv',
                        "%s/%s.csv" %
                        (URLS.TRELLIS_DOWNLOAD_ASTEXT, KEY.TRELLIS)
                    ],
                    [
                        "text/csv, decimal comma",
                        "%s/%s.csv" %
                        (URLS.TRELLIS_DOWNLOAD_ASTEXT_EU, KEY.TRELLIS)
                    ],
                ]
            }
        },
        KEY.GMDBPLOT: {
            'form': GmdbPlotView.formclass().to_rendering_dict(),
            'url': URLS.GMDBPLOT_RESTAPI
        },
        KEY.RESIDUALS: {
            'form': ResidualsView.formclass().to_rendering_dict(),
            'url': URLS.RESIDUALS_RESTAPI,
            'urls': {
                # download* below must be pairs of [key, url]. Each url
                # must return a
                # response['Content-Disposition'] = 'attachment; filename=%s'
                # url can also start with 'file:///' telling the frontend
                # to simply download the data
                'downloadRequest': [
                    [
                        'json',
                        "%s/%s.request.json" %
                        (URLS.RESIDUALS_DOWNLOAD_REQ, KEY.RESIDUALS)
                    ],
                    [
                        'yaml',
                        "%s/%s.request.yaml" %
                        (URLS.RESIDUALS_DOWNLOAD_REQ, KEY.RESIDUALS)
                    ],
                ],
                'downloadResponse':
                [['json', 'file:///%s.json' % KEY.RESIDUALS],
                 [
                     'text/csv',
                     "%s/%s.csv" %
                     (URLS.RESIDUALS_DOWNLOAD_ASTEXT, KEY.RESIDUALS)
                 ],
                 [
                     "text/csv, decimal comma",
                     "%s/%s.csv" %
                     (URLS.RESIDUALS_DOWNLOAD_ASTEXT_EU, KEY.RESIDUALS)
                 ]]
            }
        },
        KEY.TESTING: {
            'form': TestingView.formclass().to_rendering_dict(),
            'url': URLS.TESTING_RESTAPI,
            'urls': {
                # download* below must be pairs of [key, url]. Each url
                # must return a
                # response['Content-Disposition'] = 'attachment; filename=%s'
                # url can also start with 'file:///' telling the frontend
                # to simply download the data
                'downloadRequest': [
                    [
                        'json',
                        "%s/%s.request.json" %
                        (URLS.TESTING_DOWNLOAD_REQ, KEY.TESTING)
                    ],
                    [
                        'yaml',
                        "%s/%s.request.yaml" %
                        (URLS.TESTING_DOWNLOAD_REQ, KEY.TESTING)
                    ]
                ],
                'downloadResponse':
                [['json', 'file:///%s.json' % KEY.TESTING],
                 [
                     'text/csv',
                     "%s/%s.csv" % (URLS.TESTING_DOWNLOAD_ASTEXT, KEY.TESTING)
                 ],
                 [
                     "text/csv, decimal comma",
                     "%s/%s.csv" %
                     (URLS.TESTING_DOWNLOAD_ASTEXT_EU, KEY.TESTING)
                 ]]
            }
        },
        KEY.DOC: {
            'src': URLS.DOC_PAGE
        }
    }

    # REMOVE LINES BELOW!!!
    if settings.DEBUG:
        gsimnames = [
            'AkkarEtAlRjb2014', 'BindiEtAl2014Rjb', 'BooreEtAl2014',
            'CauzziEtAl2014'
        ]
        trellisformdict = components_props['trellis']['form']
        trellisformdict['gsim']['val'] = gsimnames
        trellisformdict['imt']['val'] = ['PGA']
        trellisformdict['magnitude']['val'] = "5:7"
        trellisformdict['distance']['val'] = "10 50 100"
        trellisformdict['aspect']['val'] = 1
        trellisformdict['dip']['val'] = 60
        trellisformdict['plot_type']['val'] = 's'

        residualsformdict = components_props['residuals']['form']
        residualsformdict['gsim']['val'] = gsimnames
        residualsformdict['imt']['val'] = ['PGA', 'SA']
        residualsformdict['sa_period']['val'] = "0.2 1.0 2.0"
        residualsformdict['selexpr']['val'] = "magnitude > 5"
        residualsformdict['plot_type']['val'] = 'res'

        testingformdict = components_props['testing']['form']
        testingformdict['gsim']['val'] = gsimnames
        testingformdict['imt']['val'] = ['PGA', 'SA']
        testingformdict['sa_period']['val'] = "0.2 1.0 2.0"

        components_props['testing']['form']['fit_measure']['val'] = [
            'res', 'lh', 'llh', 'mllh', 'edr'
        ]

    # remove lines above!
    gsims = json.dumps({_[0]: _[1:] for _ in aval_gsims(asjsonlist=True)})
    return render(
        request, 'egsim.html', {
            **_COMMON_PARAMS, 'sel_component': sel_component,
            'components': components_tabs,
            'component_props': json.dumps(components_props),
            'gsims': gsims,
            'server_error_message': ""
        })
示例#6
0
 def __init__(self, **kwargs):
     kwargs.setdefault('choices',
                       LazyCached(lambda: [(_, _) for _ in aval_gsims()]))
     kwargs.setdefault('label', 'Ground Shaking Intensity Model(s)')
     super(GsimField, self).__init__(**kwargs)
示例#7
0
文件: views.py 项目: rizac/eGSIM
def main(request, selected_menu=None):
    '''view for the main page'''

    # Tab components (one per tab, one per activated vue component)
    # (key, label and icon) (the last is bootstrap fontawesome name)
    components_tabs = [(KEY.HOME, TITLES.HOME, ICONS.HOME),
                       (KEY.GSIMS, TITLES.GSIMS, ICONS.GSIMS),
                       (KEY.TRELLIS, TITLES.TRELLIS, ICONS.TRELLIS),
                       (KEY.GMDBPLOT, TITLES.GMDBPLOT, ICONS.GMDBPLOT),
                       (KEY.RESIDUALS, TITLES.RESIDUALS, ICONS.RESIDUALS),
                       (KEY.TESTING, TITLES.TESTING, ICONS.TESTING),
                       (KEY.DOC, TITLES.DOC, ICONS.DOC)]
    # this can be changed if needed:
    sel_component = KEY.HOME if not selected_menu else selected_menu

    # properties to be passed to vuejs components.
    # If you change THE KEYS of the dict here you should change also the
    # javascript:
    components_props = {
        KEY.HOME: {
            'src': URLS.HOME_PAGE
        },
        KEY.GSIMS: {
            'form': GsimsView.formclass().to_rendering_dict(),
            'url': URLS.GSIMS_RESTAPI,
            'urls': {
                'getTectonicRegionalisations': URLS.GSIMS_TR
            }
        },
        KEY.TRELLIS: {
            'form': TrellisView.formclass().to_rendering_dict(),
            'url': URLS.TRELLIS_RESTAPI,
            'urls': {
                # the lists below must be made of elements of
                # the form [key, url]. For each element the JS library (VueJS)
                # will then create a POST data and issue a POST request
                # at the given url (see JS code for details).
                # Convention: If url is a JSON-serialized string representing
                # the dict: '{"file": <str>, "mimetype": <str>}'
                # then we will simply donwload the POST data without calling
                # the server.
                # Otherwise, when url denotes a Django view, remember
                # that the function should build a response with
                # response['Content-Disposition'] = 'attachment; filename=%s'
                # to tell the browser to download the content.
                # (it is just for safety, remember that we do not care because
                # we will download data in AJAX calls which do not care about
                # content disposition
                'downloadRequest':
                [[
                    'json',
                    "{0}/{1}/{1}.config.json".format(URLS.DOWNLOAD_CFG,
                                                     KEY.TRELLIS)
                ],
                 [
                     'yaml',
                     "{0}/{1}/{1}.config.yaml".format(URLS.DOWNLOAD_CFG,
                                                      KEY.TRELLIS)
                 ]],
                'downloadResponse': [
                    [
                        'json',
                        '{"file": "%s.json", "mimetype": "application/json"}' %
                        KEY.TRELLIS
                    ],
                    [
                        'text/csv',
                        "{0}/{1}/{1}.csv".format(URLS.DOWNLOAD_ASTEXT,
                                                 KEY.TRELLIS)
                    ],
                    [
                        "text/csv, decimal comma",
                        "{0}/{1}/{1}.csv".format(URLS.DOWNLOAD_ASTEXT_EU,
                                                 KEY.TRELLIS)
                    ],
                ],
                'downloadImage': [[
                    'png (visible plots only)',
                    "%s/%s.png" % (URLS.DOWNLOAD_ASIMG, KEY.TRELLIS)
                ],
                                  [
                                      'pdf (visible plots only)',
                                      "%s/%s.pdf" %
                                      (URLS.DOWNLOAD_ASIMG, KEY.TRELLIS)
                                  ],
                                  [
                                      'eps (visible plots only)',
                                      "%s/%s.eps" %
                                      (URLS.DOWNLOAD_ASIMG, KEY.TRELLIS)
                                  ],
                                  [
                                      'svg (visible plots only)',
                                      "%s/%s.svg" %
                                      (URLS.DOWNLOAD_ASIMG, KEY.TRELLIS)
                                  ]]
            }
        },
        KEY.GMDBPLOT: {
            'form': GmdbPlotView.formclass().to_rendering_dict(),
            'url': URLS.GMDBPLOT_RESTAPI
        },
        KEY.RESIDUALS: {
            'form': ResidualsView.formclass().to_rendering_dict(),
            'url': URLS.RESIDUALS_RESTAPI,
            'urls': {
                # download* below must be pairs of [key, url]. Each url
                # must return a
                # response['Content-Disposition'] = 'attachment; filename=%s'
                # url can also start with 'file:///' telling the frontend
                # to simply download the data
                'downloadRequest': [
                    [
                        'json', "{0}/{1}/{1}.config.json".format(
                            URLS.DOWNLOAD_CFG, KEY.RESIDUALS)
                    ],
                    [
                        'yaml', "{0}/{1}/{1}.config.yaml".format(
                            URLS.DOWNLOAD_CFG, KEY.RESIDUALS)
                    ],
                ],
                'downloadResponse': [
                    [
                        'json',
                        '{"file": "%s.json", "mimetype": "application/json"}' %
                        KEY.RESIDUALS
                    ],
                    [
                        'text/csv',
                        "{0}/{1}/{1}.csv".format(URLS.DOWNLOAD_ASTEXT,
                                                 KEY.RESIDUALS)
                    ],
                    [
                        "text/csv, decimal comma",
                        "{0}/{1}/{1}.csv".format(URLS.DOWNLOAD_ASTEXT_EU,
                                                 KEY.RESIDUALS)
                    ]
                ],
                'downloadImage': [[
                    'png (visible plots only)',
                    "%s/%s.png" % (URLS.DOWNLOAD_ASIMG, KEY.RESIDUALS)
                ],
                                  [
                                      'pdf (visible plots only)',
                                      "%s/%s.pdf" %
                                      (URLS.DOWNLOAD_ASIMG, KEY.RESIDUALS)
                                  ],
                                  [
                                      'eps (visible plots only)',
                                      "%s/%s.eps" %
                                      (URLS.DOWNLOAD_ASIMG, KEY.RESIDUALS)
                                  ],
                                  [
                                      'svg (visible plots only)',
                                      "%s/%s.svg" %
                                      (URLS.DOWNLOAD_ASIMG, KEY.RESIDUALS)
                                  ]]
            }
        },
        KEY.TESTING: {
            'form': TestingView.formclass().to_rendering_dict(),
            'url': URLS.TESTING_RESTAPI,
            'urls': {
                # download* below must be pairs of [key, url]. Each url
                # must return a
                # response['Content-Disposition'] = 'attachment; filename=%s'
                # url can also start with 'file:///' telling the frontend
                # to simply download the data
                'downloadRequest': [[
                    'json',
                    "{0}/{1}/{1}.config.json".format(URLS.DOWNLOAD_CFG,
                                                     KEY.TESTING)
                ],
                                    [
                                        'yaml',
                                        "{0}/{1}/{1}.config.yaml".format(
                                            URLS.DOWNLOAD_CFG, KEY.TESTING)
                                    ]],
                'downloadResponse': [
                    [
                        'json',
                        '{"file": "%s.json", "mimetype": "application/json"}' %
                        KEY.TESTING
                    ],
                    [
                        'text/csv',
                        "{0}/{1}/{1}.csv".format(URLS.DOWNLOAD_ASTEXT,
                                                 KEY.TESTING)
                    ],
                    [
                        "text/csv, decimal comma",
                        "{0}/{1}/{1}.csv".format(URLS.DOWNLOAD_ASTEXT_EU,
                                                 KEY.TESTING)
                    ]
                ]
            }
        },
        KEY.DOC: {
            'src': URLS.DOC_PAGE
        }
    }

    # Yes, what we are about to do it's really bad practice. But when in debug
    # mode, we want to easily test the frontend with typical configurations
    # already setup. In production, we will not enter here:
    if settings.DEBUG:
        gsimnames = [
            'AkkarEtAlRjb2014', 'BindiEtAl2014Rjb', 'BooreEtAl2014',
            'CauzziEtAl2014'
        ]
        trellisformdict = components_props['trellis']['form']
        trellisformdict['gsim']['val'] = gsimnames
        trellisformdict['imt']['val'] = ['PGA']
        trellisformdict['magnitude']['val'] = "5:7"
        trellisformdict['distance']['val'] = "10 50 100"
        trellisformdict['aspect']['val'] = 1
        trellisformdict['dip']['val'] = 60
        trellisformdict['plot_type']['val'] = 's'

        residualsformdict = components_props['residuals']['form']
        residualsformdict['gsim']['val'] = gsimnames
        residualsformdict['imt']['val'] = ['PGA', 'SA']
        residualsformdict['sa_period']['val'] = "0.2 1.0 2.0"
        residualsformdict['selexpr']['val'] = "magnitude > 5"
        residualsformdict['plot_type']['val'] = 'res'

        testingformdict = components_props['testing']['form']
        testingformdict['gsim']['val'] = gsimnames + ['AbrahamsonSilva2008']
        testingformdict['imt']['val'] = ['PGA', 'PGV']
        testingformdict['sa_period']['val'] = "0.2 1.0 2.0"

        components_props['testing']['form']['fit_measure']['val'] = [
            'res',
            'lh',
            # 'llh',
            # 'mllh',
            # 'edr'
        ]

    # setup browser detection
    allowed_browsers = [['Chrome', 49], ['Firefox', 45], ['Safari', 10]]
    invalid_browser_message = ('Some functionalities might not work '
                               'correctly. In case, please use any of the '
                               'following tested browsers: %s' %
                               ', '.join('%s &ge; %d' % (brw, ver)
                                         for brw, ver in allowed_browsers))

    gsims = json.dumps({_[0]: _[1:] for _ in aval_gsims(asjsonlist=True)})
    return render(
        request, 'egsim.html', {
            **_COMMON_PARAMS, 'sel_component': sel_component,
            'components': components_tabs,
            'component_props': json.dumps(components_props),
            'gsims': gsims,
            'allowed_browsers': allowed_browsers,
            'invalid_browser_message': invalid_browser_message
        })