def details(siret): """ Display the details of an office. In case the context of a rome_code is given, display appropriate score value for this rome_code """ fix_csrf_session() rome_code = request.args.get('rome_code', None) company = Office.query.filter_by(siret=siret).first() if not company: abort(404) # Check if company is hidden by SAVE if not company.score: abort(404) context = { 'company': company, 'rome_code': rome_code, 'next_url_modal': url_for('jepostule.application', siret=siret, rome_code=rome_code), } activity.log( event_name='details', siret=siret, ) return render_template('office/details.html', **context)
def results_by_commune_and_rome(commune_id, rome_id): """ Convenience function to be used by Pôle Emploi, Bob Emploi and other partners Redirects internally to our real user-facing url displaying results for his search. For more information about the differences between commune_id and zipcode, please consult README file """ fix_csrf_session() try: rome_description = settings.ROME_DESCRIPTIONS[rome_id.upper()] slugified_rome_description = slugify(rome_description) except KeyError: rome_description = None city = geocoding.get_city_by_commune_id(commune_id) if not city or not rome_description: abort(404) params = request.args.copy() params['city'] = city['slug'] params['zipcode'] = city['zipcode'] params['occupation'] = slugified_rome_description url = url_for('search.entreprises', **params) # Pass all GET params to the redirect URL: this will allow users of the API to build web links # roughly equivalent to the result of an API call - see Trello #971. return redirect(url)
def details(siret): """ Display the details of an office. In case the context of a rome_code is given, display appropriate score value for this rome_code """ fix_csrf_session() rome_code = request.args.get('rome_code', None) company = Office.query.filter_by(siret=siret).first() if not company: abort(404) # Check if company is hidden by SAVE if not company.score: abort(404) context = { 'company': company, 'rome_code': rome_code, 'hide_memo_introjs': True, } activity.log( event_name='details', siret=siret, # GA tracking is used in PSE 2019-2020 experiment utm_medium=request.args.get('utm_medium', ''), utm_source=request.args.get('utm_source', ''), utm_campaign=request.args.get('utm_campaign', ''), ) return render_template('office/details.html', **context)
def home(): fix_csrf_session() activity.log(event_name='home', ) refresh_token_result = attempt_to_refresh_peam_token() if refresh_token_result["token_has_expired"]: return redirect(refresh_token_result["redirect_url"]) return render_template('home.html', form=CompanySearchForm())
def home(): fix_csrf_session() activity.log( event_name='home', # GA tracking is used in PSE 2019-2020 experiment utm_medium=request.args.get('utm_medium', ''), utm_source=request.args.get('utm_source', ''), utm_campaign=request.args.get('utm_campaign', ''), ) return render_template('home.html', form=CompanySearchForm())
def change_info(): """ Let a user fill a form to identify himself/herself in order to request changes about an office. """ fix_csrf_session() form = forms.OfficeIdentificationForm() # Clear session if user comes from 'I don't have recruiter account' if request.args.get('no_pe_connect', ''): peam_recruiter.clear_pe_connect_recruiter_session() # Siret information is present when coming from change_info route. # Apply it only if user has not typed anything else yet. params = request.args.copy() action_name = get_action_name() if not action_name: flash('Une erreur inattendue est survenue, veuillez sélectionner à nouveau une action', 'error') return redirect(url_for('contact_form.ask_action')) form.last_name.data = form.last_name.data or session.get(peam_recruiter.SessionKeys.LASTNAME.value) form.first_name.data = form.first_name.data or session.get(peam_recruiter.SessionKeys.FIRSTNAME.value) form.email.data = form.email.data or session.get(peam_recruiter.SessionKeys.EMAIL.value) siret = params.get('siret') if siret and not form.data['siret']: form.siret.data = siret if form.validate_on_submit(): office = models.Office.query.filter(models.Office.siret == form.siret.data).first() if not office: flash(unknown_siret_message(), 'error') else: params = {key: form.data[key] for key in ['siret', 'last_name', 'first_name', 'phone', 'email']} if is_recruiter_from_lba(): params.update({"origin":"labonnealternance"}) action_form_url = "contact_form.%s_form" % action_name url = url_for(action_form_url, **params) return redirect(url) return render_template('contact_form/form.html', title='Identifiez-vous', submit_text='suivant', extra_submit_class='identification-form', form=form, is_certified_recruiter=peam_recruiter.is_certified_recruiter(), is_recruiter=peam_recruiter.is_recruiter(), use_lba_template=is_recruiter_from_lba(), show_disclaimer=True, hide_return=True, custom_ga_pageview='/recruteur/%s/identification' % action_name, )
def account(): """ The current user account main page. """ fix_csrf_session() user_social_auth = get_user_social_auth(current_user.id) context = {} if user_social_auth: context['token'] = user_social_auth.extra_data['access_token'] context['token_age_in_seconds'] = int( time.time()) - user_social_auth.extra_data['auth_time'], return render_template('user/account.html', **context)
def results_by_departments_and_rome(department_code, rome_id): """ http://localhost:8080/entreprises/departments/57/rome/M1805 Redirects internally to our real user-facing url displaying results for his search. The partners may use our API to get companies count, which comes with the URL to this route. @see labonneboite/web/api/views.py:compute_frontend_url """ fix_csrf_session() url = get_url_for_rome(rome_id, department_code) if url is None: abort(400, 'Department or rome not found') return redirect(url)
def results(city, zipcode, occupation): """ All this does is a redirect to the 'search.entreprises' view with city-related location parameters. This view is preserved so that older urls still work. """ fix_csrf_session() params = request.args.copy() params['city'] = city params['zipcode'] = zipcode params['occupation'] = occupation redirect_url = url_for('search.entreprises', **params) return redirect(redirect_url)
def logout_from_peam_callback(): """ The route where a user is redirected after a log out through the PEAM website. """ # Quick ugly but useful fix referer = request.referrer if referer and len(referer) > 1700: # If the referer is too long, the redirect will return a 502 status code. # I did not find how to change the referrer. See https://github.com/pallets/flask/issues/3310 fix_csrf_session() return render_template('home.html', form=CompanySearchForm()) return redirect(url_for('root.home'))
def details(siret): """ Display the details of an office. In case the context of a rome_code is given, display appropriate score value for this rome_code This code is very similar to the code in labonneboite/web/api/views.py """ fix_csrf_session() rome_code = request.args.get('rome_code', None) company = Office.query.filter_by(siret=siret).first() if not company: abort(404) # Alternance case alternance = 'contract' in request.args and request.args['contract'] == 'alternance' # If an office score equals 0 it means it is not supposed # to be shown on LBB frontend/api # and/or it was specifically removed via SAVE, # and thus it should not be accessible by siret. if not alternance and not company.score: # The company is hidden by SAVE abort(404) # Offices having score_alternance equal 0 may still be accessed # by siret in case of LBA offices from the visible market (i.e. having # at least one job offer obtained from the API Offers V2). # However we should not show them if they were specifically removed via SAVE. if alternance and company.is_removed_from_lba: abort(404) context = { 'company': company, 'rome_code': rome_code, 'next_url_modal': url_for('jepostule.application', siret=siret, rome_code=rome_code), } activity.log( event_name='details', siret=siret, ) return render_template('office/details.html', **context)
def favorites_list(): """ List the favorited offices of a user. """ fix_csrf_session() try: page = int(request.args.get('page')) except (TypeError, ValueError): page = 1 favorites = UserFavoriteOffice.query.filter( UserFavoriteOffice.user_id == current_user.id) limit = FAVORITES_PER_PAGE pagination = Pagination(page, limit, favorites.count()) if page > 1: favorites = favorites.offset((page - 1) * limit) favorites = favorites.limit(limit) context = { 'favorites': favorites, 'pagination': pagination, 'show_favorites': True, } return render_template('user/favorites_list.html', **context)
def account(): """ The current user account main page. """ fix_csrf_session() return render_template('user/account.html')
def home(): fix_csrf_session() return render_template('home.html', form=CompanySearchForm())
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)
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)