Module collectives.utils

Contents

Module collectives.utils#

Module collectives.utils.access#

Decorators to help manage page access.

Knowledge of python decorator is usefull to understand this module. The documentation will not cover this subject, however, there is a lot of information about decorator on Internet.

See functools

collectives.utils.access.access_requires(function, test, api=False)#

Decorator to do a test before granting access

It is a very generic decorator meant to have a way to create any test to grant access to an endpoint

Parameters:
  • f (function) – The function of the endpoint that will be protected

  • api (bool) – If True and access is denied, trigger a 403. Otherwise return an error message

  • test (function) – A function the must return a boolean. If false, access to f will be refused

Returns:

the protected (decorated) f function

Return type:

function

collectives.utils.access.confidentiality_agreement(api=False)#

Decorator which check if user has signed confidentiality agreement.

If in api mode, a failure will returns a 403 HTTP code. Else, it will redirect with a error flash message.

Parameters:

api (function) – API mode

Returns:

the protected (decorated) f function

Return type:

function

collectives.utils.access.payments_enabled(api=False)#

Decorator which checks whether the payment functionnality is enabled

Parameters:

api (bool) – If True and access is denied, trigger a 403. Otherwise return an error message

Returns:

the protected (decorated) f function

Return type:

function

collectives.utils.access.technician_required_f()#

Function to limit access to technician only.

collectives.utils.access.user_is(methods, api=False, **kwargs)#

Decorator which check if an user method to authorize access.

Example:

@user_is("is_admin")
@blueprint.route("/", methods=["GET", "POST"])
def administration():
    pass
Parameters:
  • api (bool) – If True and access is denied, trigger a 403. Otherwise return an error message

  • methods (list or string) – the user methods to call. WARNING: this parameter should always be a constant (no user generated data). Can be a list, in which case, user is authorized if method returns True (ie if user has at least one of the listed roles)

Returns:

the protected (decorated) f function

Return type:

function

collectives.utils.access.valid_user(api=False)#

Decorator which check if user is logged in and has signed current legal text.

Parameters:

api (bool) – If True and access is denied, trigger a 403. Otherwise return an error message

Returns:

the protected (decorated) f function

Return type:

function

Module collectives.utils.csv#

Module to handle csv import

collectives.utils.csv.csv_to_events(stream, description)#

Decode the csv stream to populate events.

Parameters:
  • stream (io.StringIO) – the csv file as a stream.

  • description (String) – Description template that will be used to generate new events description.

Returns:

The new events, the number of processed events, and the number of failed attempts

Return type:

list(collectives.models.event.Event), int, int

collectives.utils.csv.fill_from_csv(event, row, template)#

Fill an Event object attributes with parameter from a csv row.

Parameters:
  • event (collectives.models.event.Event) – The evet object to populate.

  • row (list(string)) – List of value from a csv file row

  • template (string) – Template for the event description. It can contains placeholders.

Returns:

Nothing

collectives.utils.csv.parse(row, column_name)#

Parse a column value in csv format to an object depending on column type. Raise an exception if field is mandatory and is not set

Parameters:
  • row (list(string)) – List of value from a csv file row

  • column_name (string) – Column name

Returns:

The parsed value

collectives.utils.csv.process_stream(base_stream, activity_type, description)#

Creates the events from a csv file.

Processing will first try to process it as an UTF8 encoded file. If it fails on a decoding error, it will try as Windows encoding (iso-8859-1).

Parameters:
  • base_stream (io.StringIO) – the csv file as a stream.

  • activity_type (collectives.models.activity_type.ActivityType) – The type of activity of the new events.

  • description (String) – Description template that will be used to generate new events description.

Returns:

The number of processed events, and the number of failed attempts

Return type:

(int, int)

Module collectives.utils.error#

Module to handle HTTP errors

collectives.utils.error.not_found(ex)#

If URL is unknown, redirect client or/and display an error message.

Parameters:

e – exception which generated the error page

Returns:

a redirection to index page

collectives.utils.error.server_error(ex)#

Return a very simple error page.

Note: in collectives, this function is only active in non debug mode.

Parameters:

e – exception which generated the error page

Returns:

error.html page with a 500 error code

Module collectives.utils.extranet#

Module to handle connexions to FFCAM extranet.

class collectives.utils.extranet.ExtranetApi#

SOAP Client to retrieve information from FFCAM servers.

property auth_info: Dict#

Builds authorization info from config

check_license(license_number: str) LicenseInfo#

Get information on a license from FFCAM server.

Parameters:

license_number (string) – License to get information about.

disabled() bool#

Check if soap client has been initialized.

If soap client has not been initialized, it means we are in dev mode.

Returns:

True if ExtranetApi is disabled.

fetch_user_info(license_number: str) UserInfo#

Get user information on a license from FFCAM server.

Parameters:

license_number – User license to get information about.

Returns:

Licence information, or None in case of API error

init_app(app: Flask)#

Initializes the API for the given Flask app

property soap_client: ServiceProxy#

Returns the cached SOAP client or initialize a new one

exception collectives.utils.extranet.ExtranetError#

An exception indicating that something has gone wrong with extranet API

exception collectives.utils.extranet.LicenseBelongsToOtherClubError#

An exception indicating that the tested license belongs to another club

class collectives.utils.extranet.LicenseInfo#

Licence information as retrieved from FFCAM servers.

exists: bool#

If licence exists.

expiry_date() date#

Get licence expiration date.

Licence expire at the start of the month LICENSE_EXPIRY_MONTH which follow the renewal date.

Returns:

License expiration date. None if renewal_date is None

is_valid_at_time(time: datetime) bool#

Check if license is valid at a given date.

Parameters:

time – Date to test license validity.

Returns:

True if license is valid

renewal_date: date#

Date of renewal of the licence.

Type:

datetime.date

class collectives.utils.extranet.UserInfo#

User information as retrieved from FFCAM servers.

date_of_birth: date#

User date of birth.

email: str#

User email.

emergency_contact_name: str#

User Emergency contact name.

emergency_contact_phone: str#

User Emergency contact phone.

first_name: str#

User first name.

is_valid: bool#

True if user exists on servers.

last_name: str#

User last name.

license_category: str#

User license category.

phone: str#

User phone.

qualite: str#

User title. “titre de civilité”.

Can be M Mme Mlle. Is used to guess gender.

collectives.utils.extranet.api: ExtranetApi = <collectives.utils.extranet.ExtranetApi object>#

ExtranetApi object that will handle request to FFCAM servers.

api requires to be initialized with ExtranetApi.init_app() to be used.

collectives.utils.extranet.sync_user(user: User, user_info: UserInfo, license_info: LicenseInfo)#

Populate a user object with user and license info from FFCAM servers.

Parameters:
  • user – User to populate.

  • user_info – User info from FFCAM server used to populate user.

  • license_info – License info from FFCAM server used to populate user.

Module collectives.utils.init#

Module to initialise DB

collectives.utils.init.activity_types(app)#

Initialize activity types

Get activity types defined in Flask application configuration (ACTIVITY_TYPES) and load it in the database. This function should be called once at app initilisation. If DB is not available, it will print a warning in stdout.

Parameters:

app – Application where to extract ACTIVITY_TYPES

Type:

flask.Application

Returns:

None

collectives.utils.init.catch_db_errors(fct, app, *args, **kwargs)#

Catches DB error in fct.

Usually, it is because the db is not already set up during DB setup. Thus, it is not very important.

Parameters:

fct – the function that will be called.

collectives.utils.init.event_types(app)#

Initialize event types

Get event types defined in Flask application configuration (EVENT_TYPES) and load it in the database. This function should be called once at app initilisation. If DB is not available, it will print a warning in stdout.

Parameters:

app – Application where to extract EVENT_TYPES

Type:

flask.Application

Returns:

None

collectives.utils.init.init_admin(app)#

Create an admin account if it does not exists. Enforce its password.

Password is config:ADMINPWD

collectives.utils.init.init_config(app, force=False, path='collectives/configuration.yaml', clean=True)#

Load configuration items at app creation.

Parameters:
  • app – Flask app used for configuration

  • force (bool) – If true, force content update. Default false

  • path (str) – Path to YAML configuration list

  • clean (bool) – If True, remove configuration which is not present in the file

collectives.utils.init.is_running_migration() bool#

Detects whether we are running a migration command.

Returns:

True if running a migration

collectives.utils.init.is_running_migration_context(ctx) bool#

Detects whether we are running a migration command.

It has not error protection if there is no context.

Parameters:

ctx (cli.Context) – The current click context

Returns:

True if running a migration

collectives.utils.init.populate_db(app)#

Populates the database with admin account and activities, if and only if we’re not currently running a db migration command

Parameters:

app (flask.Application) – The Flask application

Module collectives.utils.jinja#

Helpers functions that are make available to Jinja.

This module should not contains other functions than helpers_processors()

collectives.utils.jinja.helpers_processor()#

Function used by Jinja to access utils fonctions.

Returns:

Dictionnary of collectives.utils.time functions.

Return type:

dict(Function)

collectives.utils.jinja.map_method(function, iterable) list#

Map an iterable to one of its method

Parameters:
  • function (string) – The function name

  • function – The interable

Returns:

the result of the iterable method

Convert a git version to an http link.

Meanwhile, it also sanitize the version since it will be included as html in the page.

Parameters:

version (string) – The version as a git describe format.

Returns:

HTML string a the version wih its links.

Return type:

string

Module collectives.utils.mail#

Module to handle mail

This module handles mail sending using a SMTP server. Also, it can sign with DKIM. Conf are taken from app config:

  • config.SMTP_HOST: Hostname of SMTP server

  • config.SMTP_PORT: Connexion port to SMTP server

  • config.SMTP_ADDRESS: Sender address. Also used to set DKIM domain

  • config.SMTP_LOGIN: Login of SMTP server

  • config.SMTP_PASSWORD: Password of SMTP server

  • config.DKIM_SELECTOR: DKIM selector, usually default

  • config.DKIM_KEY: DKIM private key as PEM format

collectives.utils.mail.send_mail(**kwargs)#

Wrapper for send_mail_threaded()

collectives.utils.mail.send_mail_threaded(app, **kwargs)#

Send a mail.

Usage example:

send_mail(subject="test", email="user@example.org", message="TEST")

If email is a list, mails are sent as Cci.

Parameters:

**kwargs – See below

Keyword Arguments:
  • subject (string) – Email subject

  • email (string or list(string)) – Email Adress recipient

  • message (string) – Email body

  • error_action (function) – Function to activate if email sending fails

  • success_action (string) – Function to activate if email sending succeeds

Module collectives.utils.misc#

Miscellaneous utils functions

class collectives.utils.misc.NoDefault#

Dummy Class to know if default has been set in deepgetattr()

collectives.utils.misc.deepgetattr(obj: object, attr: str, default: Any = <collectives.utils.misc.NoDefault object>, resolve_method: bool = False) Any#

Recurses through an attribute chain to get the ultimate value.

Example: deepgetattr(role, ‘user.first_name’)

Parameters:
  • obj – The Object to get attribute from

  • attr – The attribute to get. Use dots to get attribute of an attribute

  • default – Optionnal. If no attribute is found, return this value. If default is not defined, throw an exception

  • resolve_method – Whether to call the attribute if it is a method

Returns:

the selected attribute

collectives.utils.misc.is_mobile_user()#

Read browser user agent from the request and return True if a mobile browser is detected

collectives.utils.misc.is_valid_image(file: str | IO[bytes]) bool#

Uses PIL to check whether a file is a valid image

Parameters:

file – File to verify. Path or binary stream, as accepted by PIL.Image.open()

collectives.utils.misc.sanitize_file_name(name: str) str#

Returns sanitized filename without characters that cannot be in a filename.

Basically removes all characters not alphanumerical, space, accentuated character, simple quote, dot, dash, commas, and underscore.

collectives.utils.misc.to_ascii(value)#

Convert an unicode string to ASCII, drop characters that can’t be converted

Parameters:

value (str) – Input string

Returns:

ASCII string

Return type:

str

collectives.utils.misc.truncate(value: str, max_len: int, append_ellipsis: bool = True) str#

Truncates a string so that it is no longer that a predefined length

Parameters:
  • value – string to truncate

  • max_len – maximum length of returned string

  • append_ellipsis – whether to put an ellipsis at the end of the string when it is truncated

Returns:

the truncated string

Module collectives.utils.numbers#

Module for formatting number and currencies

collectives.utils.numbers.check_phone(number: str) bool#
Returns:

True if the number if a real phone number

collectives.utils.numbers.format_bytes(size)#

Formats a size in bytes to human-readable units

Parameters:

size (int) – The size in bytes

Returns:

A string representing the amount in human-readable form, e.g. ko, Go

Return type:

string

collectives.utils.numbers.format_currency(amount)#

Formats a decimal amount in euros following the french locale

Parameters:

amount (decimal.Decimal) – The amount to be formatted

Returns:

A string representing the amount in french locale, like “1,345.67 €”

Return type:

string

collectives.utils.numbers.format_phones(phone_str: str) str#
Returns:

a regurlarly formed phone number, with proper spacing.

Parameters:

phone – A phone number

Module collectives.utils.payline#

Module to handle connexions to Payline.

class collectives.utils.payline.BuyerInfo(user: User = None)#

Information about the user making the payment

Parameters:

payment – User database entry, defaults to None

birth_date: str#

Data of birth, ptional

email: str#

Email address, must be valid

first_name: str#

Buyer first name

last_name: str#

Buyer last name

title: str#

Title, e.g. ‘M.’, ‘Mme.’, etc

class collectives.utils.payline.OrderInfo(payment: Payment = None)#

Class describing an order for a payment request.

Parameters:

payment – Payment database entry, defaults to None

amount_in_cents: int#

Amount in smallest currency unit (e.g euro cents)

date: str#

Date and time at which the order is being made, with format dd/mm/YYYY HH:MM

details: Dict[str, Any]#

Dictionary containing details about the order See https://docs.payline.com/display/DT/Object+-+orderDetail

metadata: Dict[str, Any]#

Dictionnary containing free-form metadata about the order. Used in lieu of details as Payline does not seem to acknowledge orderDetails

payment: Payment#

Related database Payment entry

private_data() Dict[str, Any]#
Returns:

Metadata in the Payline key-value list format

unique_ref() str#
Returns:

An unique reference for the order

collectives.utils.payline.PAYLINE_VERSION = 26#

Version of payline API :type: int

collectives.utils.payline.PAYMENT_ACTION = 101#

Payment action, as per https://docs.payline.com/display/DT/Codes+-+Action 101 stand for “Authorization and Capture”

Type:

int

collectives.utils.payline.PAYMENT_MODE = 'CPT'#

Payment mode, as per https://docs.payline.com/display/DT/Codes+-+Mode CPT stands for “Full”

Type:

string

class collectives.utils.payline.PaylineApi#

SOAP Client to process payment with payline, refer to Payline docs

property directpayment_client: SoapClient#

Cached SOAP client object to connect to Payline DirectPaymentAPI.

disabled() bool#

Check if Payline merchant Id has been set. :return: True if PaylineApi is disabled.

do_refund(payment_details: PaymentDetails) RefundDetails#

Tries to refund a previously approved online payment.

Will first try a ‘reset’ call (cancel immediately the payment if it has not been debited yet), and if this fail will try a full ‘refund’ call.

Parameters:

payment_details – The payment details as returned by getWebPaymentDetails()

Returns:

An object representing the response details, or None if the API call failed

do_web_payment(order_info: OrderInfo, buyer_info: BuyerInfo) PaymentRequest#

Initiates a payment request with Payline and returns the resulting token identifier on success, or information about the error

Parameters:
  • order_info – Information about the item being ordered

  • buyer_info – Information about the user amking the order

Returns:

An object representing the API response, or None if the API call failed

property encoded_auth#

authentication string for http http_header

get_web_payment_details(token: str) PaymentDetails#

Returns the details about a payment that has been previously initiated

Parameters:

token – The unique identifer returned by the do_web_payment() call

Returns:

An object representing the payment details, or None if the API call failed

init_app(app: Flask)#

Initialize the payline with the app. :param app: Current app.

payline_access_key: str#

Payline access key (to be set in payline backoffice)

payline_contract_number: str#

payline contract number

payline_country: str#

Payline country code

payline_currency: str#

payment currency : euro = 978

payline_merchant_id: str#

Payline merchant id refer to payline account

payline_merchant_name: str#

Payline merchant name

reload_config()#

Reads current configuration, reset client if necessary

property webpayment_client: SoapClient#

Cached SOAP client object to connect to Payline WebPaymentAPI.

class collectives.utils.payline.PaymentDetails(response: Dict[str, Any])#

Class wrapping the results of a “get payment details” API request

Parameters:

response – Dictionnary containing API call result. Must contain a ‘result’ key.

amount() Decimal#
Returns:

The payment amount in decimal currency units

property authorization: Dict[str, Any]#
Returns:

The dictionary corresponding to the “authorization” par of the response

static from_metadata(raw_metadata: str) PaymentDetails#

Constructs a PaymentDetails object from a metadata string

Param:

raw_metadata Json-encoded metadata string

Returns:

Payment details

property payment: Dict[str, Any]#

See https://docs.payline.com/display/DT/Object+-+payment

Returns:

The dictionary corresponding to the “payment” par of the response

raw_metadata() str#
Returns:

the raw response dictionary as a Json string

response: Dict[str, Any]#

Dictionary containing the raw SOAP api response for a payment details query. See https://docs.payline.com/display/DT/Webservice+-+getWebPaymentDetailsResponse

result: PaymentResult#

Whether the request has succeeded, or why it has not

property transaction: Dict[str, Any]#

See https://docs.payline.com/display/DT/Object+-+transaction

Returns:

The dictionary corresponding to the “transaction” par of the response

class collectives.utils.payline.PaymentRequest#

Response from a payment creation request

redirect_url#

URL on which the shopper’s browser must be redirected to make the payment.

result: PaymentResult#

Whether the request has succeeded, or why it has not

token: str#

Time stamped token that identifies the merchant’s web payment request

class collectives.utils.payline.PaymentResult(response: Dict[str, str] = None)#

Result returned after payline API requests. Gives information about whether the request has succeeded, or why it has not

Parameters:

response – response dictionary from SOAP endpoint

code#

Return code, see https://docs.payline.com/display/DT/Return+codes

is_accepted() bool#

Checks whether the call was successful

Returns:

True if successful (return message ‘ACCEPTED’), False for any other

long_message#

long message of transaction status details

payment_status() PaymentStatus#

Maps the API response short message and code to a value from our PaymentStatus enum.

Returns:

The corresponding payment status

short_message#

short message of transaction status i.e. : ACCEPTED, REFUSED, ERROR… See https://docs.payline.com/display/DT/Codes+-+ShortMessage

class collectives.utils.payline.RefundDetails(response: Dict[str, Any])#

Class wrapping the results of a “do refund” API request

Parameters:

response – Dictionnary containing API call result. Must contain a ‘result’ key.

raw_metadata() str#
Returns:

the raw response dictionary as a Json string

response: Dict[str, Any]#

Dictionary containing the raw SOAP api response for a payment refund query. See https://docs.payline.com/display/DT/Webservice+-+doRefundResponse

result: PaymentResult#

Whether the request has succeeded, or why it has not

property transaction: Dict[str, Any]#

See https://docs.payline.com/display/DT/Object+-+transaction

Returns:

The dictionary corresponding to the “transaction” par of the response

collectives.utils.payline.api: PaylineApi = <collectives.utils.payline.PaylineApi object>#

PaylineApi object that will handle request to Payline.

api requires to be initialized with PaylineApi.init_app() to be used.

Module collectives.utils.render_markdown#

Module handling Markdown rendering.

Markdown is mainly used in event description.

collectives.utils.render_markdown.markdown_to_html(text)#

Convert a markdown text to HTML, unless is already marked as markup-safe.

Parameters:

text (String) – Markdown text.

Returns:

Converted HTML text.

Return type:

String

Module collectives.utils.time#

Module for time management and display.

collectives.utils.time.add_months(months: int, from_date: date | None = None) date#

Add months to a date.

Parameters:
  • from_date – date from which to add months (default: today)

  • months – number of months to add (may be negative)

Returns:

new date with months added

collectives.utils.time.current_time() datetime#

Return current time in the defined time zone.

See config.TZ_NAME

Datetimes are stored in naive format, assumed to always be in the correct timezone. For Python to be able to compare, dates must be stripped from the tz information from our local time

Returns:

Current time

Return type:

datetime.datetime

collectives.utils.time.format_date(value, short=False)#

Format a date. (eg “Samedi 16 février 2020”).

Parameters:
  • value (datetime.datetime or datetime.date) – Date to format

  • short (bool) – If True, display a short version of the date as format_date_short()

Returns:

Formatted date

Return type:

string

collectives.utils.time.format_date_range(start, end, short=True)#

Format a range of dates without their time. (eg “Samedi 12 février 2018 au Dimanche 13 février 2018”).

Parameters:
  • start (datetime.datetime or datetime.date) – Range start date.

  • end (datetime.datetime or datetime.date) – Range end date.

  • short (bool) – If True, display short version of the dates as format_date_short()

Returns:

Formatted date range.

Return type:

string

collectives.utils.time.format_date_short(value)#

Format a date. (eg “Sam 16 février”).

Parameters:

value (datetime.datetime or datetime.date) – Date to format

Returns:

Formatted date

Return type:

string

collectives.utils.time.format_datetime(value)#

Format a date + time. (eg “Samedi 16 février 2020 à 8h00”).

Parameters:

value (datetime.datetime) – Date to format

Returns:

Formatted date or ‘N/A’ if value is None

Return type:

string

collectives.utils.time.format_datetime_range(start, end)#

Format a range of dates. (eg “Samedi 12 février 2018 à 08:00 au Dimanche 13 février 2018 à 15:00”).

  • If start and end are the same day, day is not repeated.

  • If start and end are the same, it is not treated as a range.

  • If start and end are at 0h00, time is not formatted.

Parameters:
  • start (datetime.datetime) – Range start date.

  • end (datetime.datetime) – Range end date.

Returns:

Formatted date range.

Return type:

string

collectives.utils.time.format_time(value)#

Format a time. (eg “8h12”).

Parameters:

value (datetime.datetime) – Date to format

Returns:

Formatted date

Return type:

string

collectives.utils.time.fr_months = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']#

Months in French.

Server may not have fr_FR locale installed, for convenience, we simply define months names here

collectives.utils.time.fr_short_months = ['jan.', 'fév.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sep.', 'oct.', 'nov.', 'déc.']#

Months abbreviations in French.

Server may not have fr_FR locale installed, for convenience, we simply define months abbreviations here

collectives.utils.time.fr_week_days = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']#

Day of week in French.

Server may not have fr_FR locale installed, for convenience, we simply define days of weeks names here

collectives.utils.time.get_ffcam_year(year: date) int#
Returns:

the FFCAM year (four digits) for a given date.

EG: 5th of May 2023 -> 2022 15th of September 2023 -> 2023

collectives.utils.time.parse_api_date(date_str: str, day_only: bool = True) datetime#

Parse a date from client API calls

Expected format is YYYY-MM-DD + optional time/tz Returns a naive datetime object in the server timezone

Parameters:

day_only – If true, sets time portion of datetime to midnight

Returns None if date_str is invalid

collectives.utils.time.server_local_time()#

Alias of current_time()

collectives.utils.time.ttl_cache(ttl: int = 10)#

Decorator to cache function results for a given time-to-live (TTL) in seconds.

Parameters:

ttl – Time-to-live in seconds

Returns:

Decorated function with caching

Module collectives.utils.url#

Module which contains various helping functions for url management.

collectives.utils.url.slugify(value)#

String normalisation.

Normalizes string, converts to lowercase, removes non-alpha characters, and converts spaces to hyphens.

From Django’s “django/template/defaultfilters.py”.