示例#1
0
    def test_favorites_delete(self):
        """
        Test the deletion of a favorite.
        """
        office = Office.query.filter(Office.siret == u'00000000000003').one()
        url_list = self.url_for('user.favorites_list')
        url_delete = self.url_for('user.favorites_delete', siret=office.siret)

        # An anonymous user cannot delete a favorite.
        rv = self.app.post(url_delete)
        self.assertEqual(rv.status_code, 401)

        with self.test_request_context:

            self.login(self.user)

            # Create a favorite for the user.
            UserFavoriteOffice.create(user_id=self.user.id, office_siret=office.siret)

            rv = self.app.get(url_list)
            self.assertEqual(rv.status_code, 200)
            self.assertTrue(office.name in rv.data.decode('utf-8'))
            self.assertTrue(office.city in rv.data.decode('utf-8'))

            rv = self.app.post(url_delete)
            self.assertEqual(rv.status_code, 302)
            self.assertEqual(rv.location, url_list)  # User should be redirected to the list by default.

            rv = self.app.get(url_list)
            self.assertEqual(rv.status_code, 200)
            self.assertTrue(u'Aucun favori pour le moment.' in rv.data.decode('utf-8'))
示例#2
0
def favorites_add(siret):
    """
    Add an office to the favorites of a user.
    """

    # Since we are not using a FlaskForm but a hidden input with the token in the
    # form, CSRF validation has to be done manually.
    # CSRF validation can be disabled globally (e.g. in unit tests), so ensure that
    # `WTF_CSRF_ENABLED` is enabled before.
    if current_app.config['WTF_CSRF_ENABLED']:
        csrf.validate_csrf(request.form.get('csrf_token'))

    office = Office.query.filter_by(siret=siret).first()
    if not office:
        abort(404)

    UserFavoriteOffice.get_or_create(user=current_user, office=office)

    message = u'"%s - %s" a été ajouté à vos favoris !' % (office.name,
                                                           office.city)
    flash(Markup(message), 'success')

    next_url = request.form.get('next')
    if next_url and is_safe_url(next_url):
        return redirect(urllib.unquote(next_url))

    return redirect(url_for('user.favorites_list'))
示例#3
0
    def test_favorites_list(self):
        """
        Test favorites list.
        """
        office = Office.query.filter(Office.siret == u'00000000000004').one()
        url_list = self.url_for('user.favorites_list')

        # An anonymous user cannot access the favorites list.
        rv = self.app.get(url_list)
        self.assertEqual(rv.status_code, 401)

        with self.test_request_context:

            self.login(self.user)

            rv = self.app.get(url_list)
            self.assertEqual(rv.status_code, 200)
            self.assertTrue(u'Aucun favori pour le moment.' in rv.data.decode('utf-8'))

            # Create a favorite for the user.
            UserFavoriteOffice.create(user_id=self.user.id, office_siret=office.siret)

            rv = self.app.get(url_list)
            self.assertEqual(rv.status_code, 200)
            self.assertTrue(office.name in rv.data.decode('utf-8'))
            self.assertTrue(office.city in rv.data.decode('utf-8'))
示例#4
0
def favorites_add(siret: str, rome_code: Optional[str] = None):
    """
    Add an office to the favorites of a user.
    """
    # Since we are not using a FlaskForm but a hidden input with the token in the
    # form, CSRF validation has to be done manually.
    # CSRF validation can be disabled globally (e.g. in unit tests), so ensure that
    # `WTF_CSRF_ENABLED` is enabled before.
    if current_app.config['WTF_CSRF_ENABLED']:
        csrf.validate_csrf(request.form.get('csrf_token'))

    office = Office.query.filter_by(siret=siret).first()
    if not office:
        abort(404)

    UserFavoriteOffice.add_favorite(user=current_user,
                                    office=office,
                                    rome_code=rome_code)

    message = '"%s - %s" a été ajouté à vos favoris !' % (office.name,
                                                          office.city)
    flash(Markup(message), 'success')
    activity.log('ajout-favori', siret=siret)

    return get_redirect_after_favorite_operation()
    def setUp(self):
        """
        Populate the DB with data required for these tests to work.
        """
        super(UserAccountTest, self).setUp()

        self.user = User(email='*****@*****.**',
                         gender='male',
                         first_name='John',
                         last_name='Doe')
        db_session.add(self.user)
        db_session.flush()

        self.office1 = Office(
            departement='57',
            siret='00000000000001',
            company_name='1',
            headcount='5',
            city_code='57070',
            zipcode='57070',
            naf='4646Z',
            score=90,
            x=6.166667,
            y=49.133333,
        )
        self.office2 = Office(
            departement='57',
            siret='00000000000002',
            company_name='1',
            headcount='5',
            city_code='57070',
            zipcode='57070',
            naf='4646Z',
            score=90,
            x=6.166667,
            y=49.133333,
        )
        db_session.add_all([self.office1, self.office2])
        db_session.flush()

        self.user_social_auth = UserSocialAuth(
            provider=PEAMOpenIdConnect.name,
            extra_data={'id_token': 'fake'},
            user_id=self.user.id,
        )
        self.fav1 = UserFavoriteOffice(user_id=self.user.id,
                                       office_siret=self.office1.siret)
        self.fav2 = UserFavoriteOffice(user_id=self.user.id,
                                       office_siret=self.office2.siret)
        db_session.add_all([self.user_social_auth, self.fav1, self.fav2])
        db_session.flush()

        db_session.commit()

        self.assertEqual(db_session.query(User).count(), 1)
        self.assertEqual(db_session.query(Office).count(), 2)
        self.assertEqual(db_session.query(UserFavoriteOffice).count(), 2)
        self.assertEqual(db_session.query(UserSocialAuth).count(), 1)
示例#6
0
    def test_favorites_download_list_as_pdf(self):
        url_favorites_download = self.url_for('user.favorites_list_as_pdf')
        office = Office.query.filter(Office.siret == '00000000000001').one()
        UserFavoriteOffice.create(user_id=self.user.id,
                                  office_siret=office.siret)

        with self.test_request_context():
            self.login(self.user)
            rv = self.app.get(url_favorites_download)

        self.assertEqual(rv.status_code, 200)
        self.assertEqual('application/pdf', rv.mimetype)
        # Unfortunately, it's difficult to do any more testing on the content of the pdf
        self.assertLess(1000, len(rv.data))
示例#7
0
    def test_favorites_delete(self):
        """
        Test the deletion of a favorite.
        """
        office = Office.query.filter(Office.siret == '00000000000003').one()
        url_list = self.url_for('user.favorites_list')
        url_delete = self.url_for('user.favorites_delete', siret=office.siret)
        url_search_without_domain = '/entreprises/nancy-54100/strategie-commerciale'
        url_search_with_domain = 'http://labonneboite.pole-emploi.fr' + url_search_without_domain

        # An anonymous user cannot delete a favorite.
        rv = self.app.post(url_delete)
        self.assertEqual(rv.status_code, 401)

        with self.test_request_context():

            self.login(self.user)

            # Create a favorite for the user.
            UserFavoriteOffice.create(user_id=self.user.id,
                                      office_siret=office.siret)

            rv = self.app.get(url_list)
            self.assertEqual(rv.status_code, 200)
            self.assertTrue(office.name in rv.data.decode('utf-8'))
            self.assertTrue(office.city in rv.data.decode('utf-8'))

            # Deleting favorite without next_url :
            # User should be redirected to the favorites list by default.
            rv = self.app.post(url_delete)
            self.assertEqual(rv.status_code, 302)
            self.assertEqual(rv.location, url_list)

            # Create again the favorite for the user.
            UserFavoriteOffice.create(user_id=self.user.id,
                                      office_siret=office.siret)

            # Deleting favorite from search results - the realistic case.
            # User should be redirected back to the search results.
            rv = self.app.post(url_delete,
                               data={'next': url_search_without_domain})
            self.assertEqual(rv.status_code, 302)
            self.assertEqual(rv.location, url_search_with_domain)

            rv = self.app.get(url_list)
            self.assertEqual(rv.status_code, 200)
            self.assertTrue(
                'Aucun favori pour le moment.' in rv.data.decode('utf-8'))
示例#8
0
def favorites_list_as_csv():
    """
    Download as a CSV the list of the favorited offices of a user.
    """
    return make_csv_response(
        csv_text=UserFavoriteOffice.user_favs_as_csv(current_user),
        attachment_name='mes_favoris.csv',
    )
示例#9
0
    def test_favorites_download(self):
        """
        Test download favorites list as csv.
        """
        office = Office.query.filter(Office.siret == '00000000000004').one()
        url = self.url_for('user.favorites_list_as_csv')

        # An anonymous user cannot download the favorites list.
        rv = self.app.get(url)
        self.assertEqual(rv.status_code, 401)

        with self.test_request_context():

            self.login(self.user)

            # Create a favorite for the user.
            UserFavoriteOffice.create(user_id=self.user.id,
                                      office_siret=office.siret)

            rv = self.app.get(url)
            self.assertEqual(rv.status_code, 200)
            self.assertEqual('application/csv', rv.mimetype)
            self.assertIn('siret', rv.data.decode('utf-8'))
            self.assertIn(office.siret, rv.data.decode('utf-8'))
示例#10
0
def entreprises():
    """
    This view takes arguments as a query string.

    Expected arguments are those returned by get_parameters and expected by the
    selected office search form.
    """
    fix_csrf_session()
    session['search_args'] = request.args
    location, named_location = get_location(request.args)

    occupation = request.args.get('occupation', '')
    if not occupation and 'j' in request.args:
        suggestion = search_util.build_job_label_suggestions(request.args['j'],
                                                             size=1)
        occupation = suggestion[0]['occupation'] if suggestion else None

    rome = mapping_util.SLUGIFIED_ROME_LABELS.get(occupation)
    job_doesnt_exist = not rome

    # Build form
    form_kwargs = {key: val for key, val in list(request.args.items()) if val}
    form_kwargs['j'] = settings.ROME_DESCRIPTIONS.get(rome, occupation)

    if 'occupation' not in form_kwargs:
        form_kwargs['occupation'] = occupation

    if not form_kwargs.get('l') and named_location:
        # Override form location only if it is not available (e.g when user has
        # removed it from the url)
        form_kwargs['l'] = named_location.name

    if location:
        form_kwargs['lat'] = location.latitude
        form_kwargs['lon'] = location.longitude

    form = make_company_search_form(**form_kwargs)

    # Render different template if it's an ajax call
    template = 'search/results.html' if not request.is_xhr else 'search/results_content.html'

    activity_log_properties = dict(
        emploi=occupation,
        localisation={
            'nom': named_location.name if named_location else None,
            'ville': named_location.city if named_location else None,
            'codepostal': named_location.zipcode if named_location else None,
            'latitude': location.latitude if location else None,
            'longitude': location.longitude if location else None,
        },
    )

    # Stop here in case of invalid arguments
    if not form.validate() or job_doesnt_exist:
        log_search_activity(activity_log_properties)
        return render_template(template,
                               job_doesnt_exist=job_doesnt_exist,
                               form=form)

    # Convert request arguments to fetcher parameters
    parameters = get_parameters(request.args)

    # Fetch offices and alternatives
    fetcher = search_util.HiddenMarketFetcher(
        location,
        romes=[rome],
        distance=parameters['distance'],
        travel_mode=parameters['travel_mode'],
        duration=parameters['duration'],
        sort=parameters['sort'],
        from_number=parameters['from_number'],
        to_number=parameters['to_number'],
        public=parameters.get('public'),
        headcount=parameters['headcount'],
        naf=parameters['naf'],
        naf_codes=None,
        aggregate_by=['naf'],
        departments=None,
    )
    alternative_rome_descriptions = []
    naf_codes_with_descriptions = []
    offices = []
    office_count = 0
    alternative_distances = {}

    # Aggregations
    offices, aggregations = fetcher.get_offices(add_suggestions=True)
    office_count = fetcher.office_count
    alternative_distances = fetcher.alternative_distances
    alternative_rome_descriptions = fetcher.get_alternative_rome_descriptions()

    # If a filter or more are selected, the aggregations returned by fetcher.get_offices()
    # will be filtered too... To avoid that, we are doing additionnal calls (one by filter activated)
    if aggregations:
        fetcher.update_aggregations(aggregations)

    # Generates values for the NAF filter
    # aggregations could be empty if errors or empty results
    if aggregations:
        for naf_aggregate in aggregations['naf']:
            naf_description = '%s (%s)' % (settings.NAF_CODES.get(
                naf_aggregate["code"]), naf_aggregate["count"])
            naf_codes_with_descriptions.append(
                (naf_aggregate["code"], naf_description))

    duration_filter_enabled = fetcher.duration is not None

    # Pagination.
    pagination_manager = pagination.PaginationManager(
        office_count,
        fetcher.from_number,
        fetcher.to_number,
        request.full_path,
    )
    current_page = pagination_manager.get_current_page()

    # Anticipate future calls by pre-computing duration-related searches
    if location:
        precompute.isochrones((location.latitude, location.longitude))

    form.naf.choices = [('', 'Tous les secteurs')] + sorted(
        naf_codes_with_descriptions, key=lambda t: t[1])
    form.validate()

    canonical_url = get_canonical_results_url(
        named_location.zipcode, named_location.city,
        occupation) if named_location else ''

    context = {
        'alternative_distances':
        alternative_distances,
        'alternative_rome_descriptions':
        alternative_rome_descriptions,
        'canonical_url':
        canonical_url,
        'companies':
        list(offices),
        'companies_per_page':
        pagination.OFFICES_PER_PAGE,
        'company_count':
        office_count,
        'distance':
        fetcher.distance,
        'doorbell_tags':
        doorbell.get_tags('results'),
        'form':
        form,
        'headcount':
        fetcher.headcount,
        'job_doesnt_exist':
        False,
        'naf':
        fetcher.naf,
        'location':
        location,
        'city_name':
        named_location.city if named_location else '',
        'location_name':
        named_location.name if named_location else '',
        'page':
        current_page,
        'pagination':
        pagination_manager,
        'rome_code':
        rome,
        'rome_description':
        settings.ROME_DESCRIPTIONS.get(rome, ''),
        'show_favorites':
        True,
        'sort':
        fetcher.sort,
        'tile_server_url':
        settings.TILE_SERVER_URL,
        'travel_mode':
        fetcher.travel_mode,
        'travel_modes':
        maps_constants.TRAVEL_MODES,
        'travel_modes_french':
        maps_constants.TRAVEL_MODES_FRENCH,
        'duration_filter_enabled':
        duration_filter_enabled,
        'user_favs_as_sirets':
        UserFavoriteOffice.user_favs_as_sirets(current_user),
    }

    activity_log_properties['distance'] = fetcher.distance
    activity_log_properties['effectif'] = fetcher.headcount
    activity_log_properties['tri'] = fetcher.sort
    activity_log_properties['naf'] = fetcher.naf
    activity_log_sirets = [office.siret for office in offices]
    activity.log_search(sirets=activity_log_sirets,
                        count=office_count,
                        page=current_page,
                        **activity_log_properties)

    return render_template(template, **context)
示例#11
0
def results(city, zipcode, occupation):

    kwargs = get_parameters(request.args)
    kwargs['city'] = city
    kwargs['zipcode'] = zipcode
    kwargs['occupation'] = occupation

    canonical = '/entreprises/%s-%s/%s' % (city, zipcode, occupation)

    # Remove keys with empty values.
    form_kwargs = {key: val for key, val in dict(**kwargs).items() if val}

    city = city.replace('-', ' ').capitalize()
    full_location = '%s (%s)' % (city, zipcode)
    form_kwargs['location'] = full_location

    # Get ROME code (Répertoire Opérationnel des Métiers et des Emplois).
    rome = mapping_util.SLUGIFIED_ROME_LABELS.get(occupation)

    # Stop here if the ROME code does not exists.
    if not rome:
        form_kwargs['job'] = occupation
        form = CompanySearchForm(**form_kwargs)
        context = {'job_doesnt_exist': True, 'form': form}
        return render_template('search/results.html', **context)

    session['search_args'] = request.args

    # Fetch companies and alternatives.
    fetcher = search_util.Fetcher(**kwargs)
    alternative_rome_descriptions = []
    alternative_distances = {}
    location_error = False
    try:
        current_app.logger.debug("fetching companies and company_count")
        companies = fetcher.get_companies()
        for alternative, count in fetcher.alternative_rome_codes.iteritems():
            if settings.ROME_DESCRIPTIONS.get(alternative) and count:
                desc = settings.ROME_DESCRIPTIONS.get(alternative)
                slug = slugify(desc)
                alternative_rome_descriptions.append(
                    [alternative, desc, slug, count])
        company_count = fetcher.company_count
        alternative_distances = fetcher.alternative_distances
    except search_util.JobException:
        companies = []
        company_count = 0
    except search_util.LocationError:
        companies = []
        company_count = 0
        location_error = True

    # Pagination.
    from_number_param = int(kwargs.get('from') or 1)
    to_number_param = int(kwargs.get('to') or 10)
    pagination_manager = PaginationManager(company_count, from_number_param,
                                           to_number_param, request.full_path)
    current_page = pagination_manager.get_current_page()

    # Get contact mode and position.
    for position, company in enumerate(companies, start=1):
        company.contact_mode = util.get_contact_mode_for_rome_and_naf(
            fetcher.rome, company.naf)
        # position is later used in labonneboite/web/static/js/results.js
        company.position = position

    # Get NAF code and their descriptions.
    rome_2_naf_mapper = mapping_util.Rome2NafMapper()
    naf_codes = rome_2_naf_mapper.map([
        rome,
    ])
    naf_codes_with_descriptions = []
    for naf_code in naf_codes:
        naf_description = settings.NAF_CODES.get(naf_code)
        naf_codes_with_descriptions.append((naf_code, naf_description))
    if kwargs.get('naf') in [item[0] for item in naf_codes_with_descriptions]:
        naf_text = settings.NAF_CODES.get(kwargs['naf'])
        naf_codes_with_descriptions.append((kwargs['naf'], naf_text))

    form_kwargs['job'] = settings.ROME_DESCRIPTIONS[rome]
    form = CompanySearchForm(**form_kwargs)
    form.naf.choices = [('', u'Tous les secteurs')] + sorted(
        naf_codes_with_descriptions, key=lambda t: t[1])
    form.validate()

    context = {
        'alternative_distances':
        alternative_distances,
        'alternative_rome_descriptions':
        alternative_rome_descriptions,
        'canonical':
        canonical,
        'city':
        city,
        'companies':
        list(companies),
        'company_count':
        company_count,
        'distance':
        kwargs['distance'],
        'doorbell_tags':
        util.get_doorbell_tags('results'),
        'form':
        form,
        'headcount':
        kwargs['headcount'],
        'job_doesnt_exist':
        False,
        'location':
        full_location,
        'location_error':
        location_error,
        'naf':
        kwargs['naf'],
        'naf_codes':
        naf_codes_with_descriptions,
        'page':
        current_page,
        'pagination':
        pagination_manager,
        'rome_code':
        rome,
        'rome_description':
        settings.ROME_DESCRIPTIONS.get(fetcher.rome, ''),
        'show_favorites':
        True,
        'sort':
        kwargs['sort'],
        'tile_server_url':
        settings.TILE_SERVER_URL,
        'user_favs_as_sirets':
        UserFavoriteOffice.user_favs_as_sirets(current_user),
    }
    return render_template('search/results.html', **context)
示例#12
0
def entreprises():
    """
    This view takes arguments as a query string.

    Expected arguments are those returned by get_parameters and expected by the
    selected office search form.
    """
    fix_csrf_session()

    refresh_token_result = attempt_to_refresh_peam_token()
    if refresh_token_result["token_has_expired"]:
        return redirect(refresh_token_result["redirect_url"])

    # filter empty url params
    args = {
        key: request.args[key]
        for key in request.args if request.args[key] != ''
    }

    # get structured location data from url params
    location, named_location, departments = get_location(args)

    occupation = args.get('occupation', '')
    if not occupation and 'j' in args:
        suggestion = autocomplete.build_job_label_suggestions(args['j'],
                                                              size=1)
        occupation = suggestion[0]['occupation'] if suggestion else None

    rome = mapping_util.SLUGIFIED_ROME_LABELS.get(occupation)
    job_doesnt_exist = not rome

    # Related romes
    if settings.ENABLE_RELATED_ROMES:
        related_romes = None
        hide_suggestions = False
        if (named_location):
            with start_transaction(op='related_romes_get',
                                   name='rome_' + rome):
                related_area = RELATED_ROMES_AREAS.get(
                    named_location.city_code, None)
                if (related_area):
                    # Hide suggestions for places which may have suggestions
                    hide_suggestions = True
                    romes = RELATED_ROMES.get(related_area, [])
                    if (rome in romes):
                        # Case where there are suggestions for this rome and this location
                        related_romes = romes.get(rome)
                        # related_romes = list(map(add_nafs, related_romes))
                        related_romes = list(
                            map(add_descriptions, related_romes))
                        # sort and limit size
                        related_romes.sort(
                            key=lambda rome_: rome_.get('score'))
                        related_romes = related_romes[:settings.
                                                      MAX_RELATED_ROMES]
                        if (len(related_romes) > 0):
                            flash(
                                'Nouvelle fonctionnalité : Grâce aux nouveaux filtres, élargissez votre recherche aux métiers qui recrutent !',
                                'info')
    else:
        related_romes = []
        hide_suggestions = False

    # Build form
    form_kwargs = {key: val for key, val in list(args.items()) if val}
    form_kwargs['j'] = settings.ROME_DESCRIPTIONS.get(rome, occupation)

    if 'occupation' not in form_kwargs:
        form_kwargs['occupation'] = occupation

    if not form_kwargs.get('l') and named_location:
        # Override form location only if it is not available (e.g when user has
        # removed it from the url)
        form_kwargs['l'] = named_location.name

    if location:
        form_kwargs['lat'] = location.latitude
        form_kwargs['lon'] = location.longitude

    if departments:
        form_kwargs['departments'] = departments

    form = make_company_search_form(**form_kwargs)

    # Render different template if it's an ajax call
    template = 'search/results.html' if not request.is_xhr else 'search/results_content.html'

    activity_log_properties = dict(
        emploi=occupation,
        localisation={
            'nom': named_location.name if named_location else None,
            'ville': named_location.city if named_location else None,
            'codepostal': named_location.zipcode if named_location else None,
            'latitude': location.latitude if location else None,
            'longitude': location.longitude if location else None,
            'departments': departments if departments else None,
        },
    )

    # Stop here in case of invalid arguments
    if not form.validate() or job_doesnt_exist:
        log_search_activity(activity_log_properties)
        return render_template(template,
                               job_doesnt_exist=job_doesnt_exist,
                               form=form)

    # Convert request arguments to fetcher parameters
    parameters = get_parameters(args)

    # Fetch offices and alternatives
    fetcher = HiddenMarketFetcher(
        location.longitude if location is not None else None,
        location.latitude if location is not None else None,
        departments=departments.split(',') if departments else None,
        romes=[rome],
        distance=parameters['distance'],
        travel_mode=parameters['travel_mode'],
        duration=parameters['duration'],
        sort=parameters['sort'],
        from_number=parameters['from_number'],
        to_number=parameters['to_number'],
        audience=parameters['audience'],
        headcount=parameters['headcount'],
        naf=parameters['naf'],
        naf_codes=None,
        aggregate_by=['naf'],
    )
    alternative_rome_descriptions = []
    naf_codes_with_descriptions = []
    offices = []
    office_count = 0
    alternative_distances = {}

    # Aggregations
    offices, aggregations = fetcher.get_offices(add_suggestions=True)
    office_count = fetcher.office_count
    alternative_distances = fetcher.alternative_distances
    alternative_rome_descriptions = fetcher.get_alternative_rome_descriptions()

    # If a filter or more are selected, the aggregations returned by fetcher.get_offices()
    # will be filtered too... To avoid that, we are doing additionnal calls (one by filter activated)
    if aggregations:
        fetcher.update_aggregations(aggregations)

    # Generates values for the NAF filter
    # aggregations could be empty if errors or empty results
    if aggregations:
        for naf_aggregate in aggregations['naf']:
            naf_description = '%s (%s)' % (settings.NAF_CODES.get(
                naf_aggregate["code"]), naf_aggregate["count"])
            naf_codes_with_descriptions.append(
                (naf_aggregate["code"], naf_description))

    duration_filter_enabled = fetcher.duration is not None

    # Pagination.
    pagination_manager = pagination.PaginationManager(
        office_count,
        fetcher.from_number,
        fetcher.to_number,
        request.full_path,
    )
    current_page = pagination_manager.get_current_page()

    form.naf.choices = [('', 'Tous les secteurs')] + sorted(
        naf_codes_with_descriptions, key=lambda t: t[1])
    form.validate()

    canonical_url = get_canonical_results_url(
        named_location.zipcode, named_location.city,
        occupation) if named_location else ''

    context = {
        'alternative_distances':
        alternative_distances,
        'alternative_rome_descriptions':
        alternative_rome_descriptions,
        'related_romes':
        related_romes,
        'related_rome_initial':
        parameters.get('related_rome_initial', ''),
        'hide_suggestions':
        hide_suggestions,
        'canonical_url':
        canonical_url,
        'companies':
        list(offices),
        'companies_per_page':
        pagination.OFFICES_PER_PAGE,
        'company_count':
        office_count,
        'distance':
        fetcher.distance,
        'form':
        form,
        'headcount':
        fetcher.headcount,
        'job_doesnt_exist':
        False,
        'naf':
        fetcher.naf,
        'location':
        location,
        'departments':
        departments,
        'city_name':
        named_location.city if named_location else '',
        'location_name':
        named_location.name if named_location else '',
        'page':
        current_page,
        'pagination':
        pagination_manager,
        'rome_code':
        rome,
        'rome_description':
        settings.ROME_DESCRIPTIONS.get(rome, ''),
        'show_favorites':
        True,
        'sort':
        fetcher.sort,
        'tile_server_url':
        settings.TILE_SERVER_URL,
        'travel_mode':
        fetcher.travel_mode,
        'travel_modes':
        maps_constants.TRAVEL_MODES,
        'travel_modes_french':
        maps_constants.TRAVEL_MODES_FRENCH,
        'duration_filter_enabled':
        duration_filter_enabled,
        'user_favs_as_sirets':
        UserFavoriteOffice.user_favs_as_sirets(current_user),
    }

    activity_log_properties['distance'] = fetcher.distance
    activity_log_properties['effectif'] = fetcher.headcount
    activity_log_properties['tri'] = fetcher.sort
    activity_log_properties['naf'] = fetcher.naf
    activity_log_sirets = [office.siret for office in offices]
    activity.log_search(sirets=activity_log_sirets,
                        count=office_count,
                        page=current_page,
                        **activity_log_properties)

    return render_template(template, **context)