def get_sandbox(module_name, getter_name): if not hasattr(getters, module_name): errors = ApiErrors() errors.add_error( 'module', 'Il n\'existe pas de tel \"{}\" module de getters pour la sandbox'. format(module_name)) raise errors testcafes_module = getattr(getters, module_name) if not hasattr(testcafes_module, getter_name): errors = ApiErrors() errors.add_error( 'getter', 'Il n\'existe pas de tel \"{} {}\" getter pour la sandbox'.format( module_name, getter_name)) raise errors getter = getattr(testcafes_module, getter_name) try: obj = getter() return jsonify(obj) except: errors = ApiErrors() errors.add_error( 'query', 'Une erreur s\'est produite lors du calcul de \"{} {}\" pour la sandbox' .format(module_name, getter_name)) raise errors
def check_offer_is_editable(offer: Offer): if not offer.isEditable: error = ApiErrors() error.status_code = 400 error.add_error('global', "Les offres importées ne sont pas modifiables") raise error
def errors(self): api_errors = ApiErrors() columns = self.__class__.__table__.columns._data for key in columns.keys(): column = columns[key] value = getattr(self, key) if not isinstance(column, Column): continue if not column.nullable \ and not column.foreign_keys \ and not column.primary_key \ and column.default is None \ and value is None: api_errors.add_error(key, 'Cette information est obligatoire') if value is None: continue if (isinstance(column.type, String) or isinstance(column.type, CHAR)) \ and not isinstance(column.type, sqlalchemy.Enum) \ and not isinstance(value, str): api_errors.add_error(key, 'doit être une chaîne de caractères') if (isinstance(column.type, String) or isinstance(column.type, CHAR)) \ and isinstance(value, str) \ and column.type.length \ and len(value) > column.type.length: api_errors.add_error( key, 'Vous devez saisir moins de ' + str(column.type.length) + ' caractères') if isinstance(column.type, Integer) \ and not isinstance(value, int): api_errors.add_error(key, 'doit être un entier') if isinstance(column.type, Float) \ and not isinstance(value, float): api_errors.add_error(key, 'doit être un nombre') return api_errors
def check_and_save(*objects): if not objects: raise ValueError('Objects to save need to be passed as arguments' + ' to check_and_save') # CUMULATE ERRORS IN ONE SINGLE API ERRORS DURING ADD TIME api_errors = ApiErrors() for obj in objects: obj_api_errors = obj.errors() if obj_api_errors.errors.keys(): api_errors.errors.update(obj_api_errors.errors) else: db.session.add(obj) # CHECK BEFORE COMMIT if api_errors.errors.keys(): raise api_errors # COMMIT try: db.session.commit() except DataError as de: api_errors.addError(*BaseObject.restize_data_error(de)) raise api_errors except IntegrityError as ie: api_errors.addError(*BaseObject.restize_integrity_error(ie)) raise api_errors except TypeError as te: api_errors.addError(*BaseObject.restize_type_error(te)) raise api_errors except ValueError as ve: api_errors.addError(*BaseObject.restize_value_error(ve)) raise api_errors if api_errors.errors.keys(): raise api_errors
def check_offer_id_is_present_in_request(offer_id: str): if offer_id is None: errors = ApiErrors() errors.status_code = 400 errors.add_error('global', 'Le paramètre offerId est obligatoire') errors.maybe_raise() raise errors
def export_table(model_name): check_token() ae = ApiErrors() try: model = app.model[model_name] except KeyError: ae.addError('global', 'Nom de classe incorrect : '+model_name) return jsonify(ae.errors), 400 if not is_exportable(model_name): ae.addError('global', 'Classe non exportable : '+model_name) return jsonify(ae.errors), 400 objects = model.query.all() csvfile = StringIO() header = objects[0]._asdict().keys() if model_name == 'User': header = list(filter(lambda h: h!='id' and h!='password', header)) writer = csv.DictWriter(csvfile, header) writer.writeheader() for obj in objects: dct = obj._asdict() if model_name == 'User': del(dct['password']) del(dct['id']) writer.writerow(dct) csvfile.seek(0) mem = BytesIO() mem.write(csvfile.getvalue().encode('utf-8')) mem.seek(0) csvfile.close() return send_file(mem, attachment_filename='export.csv', as_attachment=True)
def errors(self): errors = ApiErrors() data = self.__class__.__table__.columns._data for key in data.keys(): col = data[key] val = getattr(self, key) if not isinstance(col, Column): continue if not col.nullable\ and not col.foreign_keys\ and not col.primary_key\ and col.default is None\ and val is None: errors.addError(key, 'Cette information est obligatoire') if val is None: continue if (isinstance(col.type, String) or isinstance(col.type, CHAR))\ and not isinstance(col.type, Enum)\ and not isinstance(val, str): errors.addError(key, 'doit etre une chaine de caracteres') if (isinstance(col.type, String) or isinstance(col.type, CHAR))\ and isinstance(val, str)\ and col.type.length\ and len(val)>col.type.length: errors.addError( key, 'Vous devez saisir moins de ' + str(col.type.length) + ' caracteres') if isinstance(col.type, Integer)\ and not isinstance(val, int): errors.addError(key, 'doit etre un entier') if isinstance(col.type, Float)\ and not isinstance(val, float): errors.addError(key, 'doit etre un nombre') return errors
def ensure_can_be_updated(self): if self.lastProvider: errors = ApiErrors() errors.add_error( 'global', 'not allowed because data come from provider ' + element.lastProvider.name ) raise errors
def get_offerer(id): dehumanize_id = dehumanize(id) for offerer in current_user.offerers: if offerer.id == dehumanize_id: return jsonify(offerer._asdict(include=OFFERERS_INCLUDES)), 200 e = ApiErrors() e.addError('global', "Cette structure n'est pas enregistrée chez cet utilisateur.") return jsonify(e.errors), 400
def ensure_current_user_has_rights(rights, offerer_id, user=current_user): if not user.hasRights(rights, offerer_id): errors = ApiErrors() errors.add_error( 'global', "Vous n'avez pas les droits d'accès suffisant pour accéder à cette information." ) errors.status_code = 403 raise errors
def internal_error(error): tb = traceback.format_exc() oneline_stack = ''.join(tb).replace('\n', ' ### ') app.logger.error('500 on %s %s — %s', request.method, request.url, oneline_stack) e = ApiErrors() e.add_error( 'global', "Il semble que nous ayons des problèmes techniques :(" + " On répare ça au plus vite.") return jsonify(e.errors), 500
def check_token(): if EXPORT_TOKEN is None or EXPORT_TOKEN == '': raise ValueError("Missing environment variable EXPORT_TOKEN") token = request.args.get('token') ae = ApiErrors() if token is None: ae.addError('token', 'Vous devez préciser un jeton dans l''adresse (token=XXX)') if not token == EXPORT_TOKEN: ae.addError('token', 'Le jeton est invalide') if ae.errors: raise ae
def list_offerers_venues(id): dehumanize_id = dehumanize(id) for offerer in current_user.offerers: if offerer.id == dehumanize_id: venues = [ o._asdict(include=VENUES_INCLUDES) for o in offerer.managedVenues ] return jsonify(venues), 200 e = ApiErrors() e.addError('global', "Cette structure n'est pas enregistrée chez cet utilisateur.") return jsonify(e.errors), 400
def check_single_order_by_string(order_by_string): order_by_string = order_by_string.strip(' ') optional_table_prefix = '("?\\w+"?\\.|)' column_identifier = '"?\\w+"?' optional_sorting_order = '(|\\s+desc|\\s+asc)' if not re.match( f'^{optional_table_prefix}{column_identifier}{optional_sorting_order}$', order_by_string, re.IGNORECASE): api_errors = ApiErrors() api_errors.add_error('order_by', 'Invalid order_by field : "%s"' % order_by_string) raise api_errors
def load_or_raise_error(obj_class, human_id): data = obj_class.query.filter_by(id=dehumanize(human_id)).first() if data is None: errors = ApiErrors() errors.add_error( 'global', 'Aucun objet ne correspond à cet identifiant dans notre base de données' ) errors.status_code = 400 raise errors else: return data
def list_venue_providers(): venueId = request.args.get('venueId') if venueId is None: e = ApiErrors() e.addError('venueId', 'Vous devez obligatoirement fournir le paramètre venueId') return jsonify(e.errors), 400 vp_query = VenueProvider.query\ .filter_by(venueId=dehumanize(venueId)) return jsonify([ vp._asdict(include=VENUE_PROVIDER_INCLUDES) for vp in vp_query.all() ])
def create_mediation(): # TODO: Allow to receive a URL from request.form['thumb'] # save_thumb already does it so it should be easy, but I can't make it ... if 'thumb' not in request.files\ or request.files['thumb'].filename == '': e = ApiErrors() e.addError('thumb', "Vous devez fournir une image d'accroche") return jsonify(e.errors), 400 thumb = request.files['thumb'] filename_parts = thumb.filename.rsplit('.', 1) if len(filename_parts)<2\ or filename_parts[1].lower() not in ALLOWED_EXTENSIONS: e = ApiErrors() e.addError('thumb', "Ce format d'image n'est pas autorisé") return jsonify(e.errors), 400 offererId = dehumanize(request.form['offererId']) ensure_current_user_has_rights(RightsType.editor, offererId) new_mediation = Mediation() new_mediation.author = current_user new_mediation.eventId = dehumanize(request.form['eventId']) new_mediation.offererId = offererId app.model.PcObject.check_and_save(new_mediation) if 'croppingRect[x]' in request.form: crop = [ float(request.form['croppingRect[x]']), float(request.form['croppingRect[y]']), float(request.form['croppingRect[height]']) ] else: crop = None new_mediation.save_thumb(thumb.read(), 0, crop=crop) return jsonify(new_mediation), 201
def handle_rest_get_list(modelClass, query=None, filter_fn=None, refine=None, order_by=None, flask_request=None, include=None, resolve=None, print_elements=None, paginate=None, page=None): if flask_request is None: flask_request = request if query is None: query = modelClass.query # DELETED if hasattr(modelClass, 'deleted'): query = query.filter_by(deleted=False) # FILTER filters = flask_request.args.copy() if filter_fn: query = filter_fn(query, filters) # REFINE if refine: query = refine(query) # ORDER BY if order_by: try: order_by = [order_by] if not isinstance(order_by, list)\ else order_by query = query.order_by(*order_by) except ProgrammingError as e: field = re.search('column "?(.*?)"? does not exist', e._message, re.IGNORECASE) if field: errors = ApiErrors() errors.addError('order_by', 'order_by value references an unknown field : '+field.group(1)) raise errors else: raise e # PAGINATE if paginate: page = 'page' in filters and filters['page'] query = query.paginate(page, per_page=paginate, error_out=False)\ .items # DICTIFY elements = list(map( lambda o: o._asdict( include=include, resolve=resolve, filters=filters ), query)) # PRINT if print_elements: print(elements) # RETURN return jsonify(elements), 200
def check_valid_edition(payload: dict): forbidden_keys = { 'idAtProviders', 'dateModifiedAtLastProvider', 'thumbCount', 'firstThumbDominantColor', 'owningOffererId', 'id', 'lastProviderId', 'dateCreated' } all_keys = payload.keys() keys_in_error = forbidden_keys.intersection(all_keys) if keys_in_error: errors = ApiErrors() for key in keys_in_error: errors.add_error(key, 'Vous ne pouvez pas modifier cette information') raise errors
def post_booking(): offer_id = request.json.get('offerId') ae = ApiErrors() if current_user.canBook == False: ae.addError('canBook', 'L\'utilisateur n\'a pas le droit de réserver d\'offre') return jsonify(ae.errors), 400 if offer_id is None: ae.addError('offerId', 'Vous devez préciser un identifiant d\'offre') return jsonify(ae.errors), 400 offer = Offer.query.filter_by(id=dehumanize(offer_id)).first() if offer is None: ae.addError('offerId', 'offerId ne correspond à aucune offer') return jsonify(ae.errors), 400 if offer.bookingLimitDatetime is not None and\ offer.bookingLimitDatetime < datetime.utcnow(): ae.addError( 'global', 'la date limite de réservation de cette offre' + ' est dépassée') return jsonify(ae.errors), 400 new_booking = Booking() new_booking.offerId = dehumanize(offer_id) token = random_token() new_booking.token = token new_booking.user = current_user recommendation_id = request.json.get('recommendationId') if recommendation_id is not None: new_booking.recommendationId = dehumanize(recommendation_id) try: app.model.PcObject.check_and_save(new_booking) except InternalError as ie: if 'check_booking' in str(ie.orig): ae.addError( 'global', 'la quantité disponible pour cette offre' + ' est atteinte') return jsonify(ae.errors), 400 else: raise ie send_booking_recap_emails(app.model.Offer.query.get(new_booking.offerId), new_booking) return jsonify(new_booking._asdict(include=BOOKINGS_INCLUDES, venueTz=True)), 201
def save(*objects): if not objects: return None # CUMULATE ERRORS IN ONE SINGLE API ERRORS DURING ADD TIME api_errors = ApiErrors() for obj in objects: with db.session.no_autoflush: obj_api_errors = obj.errors() if obj_api_errors.errors.keys(): api_errors.errors.update(obj_api_errors.errors) else: db.session.add(obj) # CHECK BEFORE COMMIT if api_errors.errors.keys(): raise api_errors # COMMIT try: db.session.commit() except DataError as de: api_errors.add_error(*PcObject.restize_data_error(de)) db.session.rollback() raise api_errors except IntegrityError as ie: api_errors.add_error(*PcObject.restize_integrity_error(ie)) db.session.rollback() raise api_errors except InternalError as ie: for obj in objects: api_errors.add_error(*obj.restize_internal_error(ie)) db.session.rollback() raise api_errors except TypeError as te: api_errors.add_error(*PcObject.restize_type_error(te)) db.session.rollback() raise api_errors except ValueError as ve: api_errors.add_error(*PcObject.restize_value_error(ve)) db.session.rollback() raise api_errors if api_errors.errors.keys(): raise api_errors
def get_user_with_credentials(identifier: str, password: str) -> User: with auto_close_db_transaction(): user = find_user_by_email(identifier) errors = ApiErrors() errors.status_code = 401 if not user: errors.add_error('identifier', 'Identifiant incorrect') raise errors if not user.isValidated: errors.add_error('identifier', "Ce compte n'est pas validé.") raise errors if not user.checkPassword(password): errors.add_error('password', 'Mot de passe incorrect') raise errors return user
def edit_offer(offer_id): updated_offer_dict = request.json query = Offer.query.filter_by(id=dehumanize(offer_id)) offer = query.first_or_404() ensure_provider_can_update(offer) update(offer, updated_offer_dict) try: app.model.PcObject.check_and_save(offer) except InternalError as ie: if 'check_offer' in str(ie.orig): ae = ApiErrors() ae.addError( 'available', 'la quantité pour cette offre' + ' ne peut pas être inférieure' + ' au nombre de réservations existantes.') return jsonify(ae.errors), 400 else: raise ie return jsonify(offer._asdict(include=OFFERS_INCLUDES)), 200
def query_with_order_by(query, order_by): if order_by: if type(order_by) == str: order_by = text(order_by) try: order_by = [order_by] if not isinstance(order_by, list) \ else order_by query = query.order_by(*order_by) except ProgrammingError as e: field = re.search('column "?(.*?)"? does not exist', e._message, re.IGNORECASE) if field: errors = ApiErrors() errors.add_error( 'order_by', 'order_by value references an unknown field : ' + field.group(1)) raise errors else: raise e return query
def get_user_with_credentials(identifier, password): errors = ApiErrors() errors.status_code = 401 if identifier is None: errors.addError('identifier', 'Identifiant manquant') if password is None: errors.addError('password', 'Mot de passe manquant') errors.maybeRaise() user = User.query.filter_by(email=identifier).first() if not user: errors.addError('identifier', 'Identifiant incorrect') raise errors if not user.checkPassword(password): errors.addError('password', 'Mot de passe incorrect') raise errors login_user(user, remember=True) return user
def export_table(model_name): _check_token() ae = ApiErrors() if model_name not in models.__all__: ae.add_error('global', 'Classe inconnue : ' + model_name) return jsonify(ae.errors), 400 try: model = getattr(models, model_name) except KeyError: ae.add_error('global', 'Nom de classe incorrect : ' + model_name) return jsonify(ae.errors), 400 if not _is_exportable(model_name): ae.add_error('global', 'Classe non exportable : ' + model_name) return jsonify(ae.errors), 400 objects = model.query.all() if len(objects) == 0: return "", 200 csvfile = StringIO() header = _clean_dict_for_export(model_name, as_dict(objects[0])).keys() if model_name == 'User': header = list(filter(lambda h: h != 'id' and h != 'password', header)) writer = csv.DictWriter(csvfile, header, extrasaction='ignore') writer.writeheader() for obj in objects: dct = _clean_dict_for_export(model_name, as_dict(obj)) writer.writerow(dct) csvfile.seek(0) mem = BytesIO() mem.write(csvfile.getvalue().encode('utf-8')) mem.seek(0) csvfile.close() return send_file(mem, attachment_filename='export.csv', as_attachment=True)
def find_or_make_recommendation(user, occasion_type, occasion_id, mediation_id, from_user_id=None): query = Recommendation.query print('(special) offer_id', occasion_id, 'mediation_id', mediation_id) if not mediation_id and not (occasion_id and occasion_type): return None if mediation_id: filter = (Recommendation.mediationId == mediation_id) elif occasion_id and occasion_type: if occasion_type == 'thing': filter = (Recommendation.thingId == mediation_id) elif occasion_type == 'event': filter = (Recommendation.eventId == mediation_id) else: ae = ApiErrors() ae.addError('occasion_type', "Invalid occasion type : " + occasion_type) raise ae requested_recommendation = query.filter(filter & (Recommendation.userId==user.id))\ .first() if requested_recommendation is None: if mediation_id: return None elif occasion_type == 'thing': occasion = Thing.query.get(occasion_id) elif occasion_type == 'event': occasion = Event.query.get(occasion_id) mediation = Mediation.query.get(mediation_id) requested_recommendation = create_recommendation(user, occasion, mediation=mediation) return requested_recommendation
def decimal_cast_error(error): api_errors = ApiErrors() app.logger.warning(json.dumps(error.errors)) for field in error.errors.keys(): api_errors.add_error(field, 'Saisissez un nombre valide') return jsonify(api_errors.errors), 400
def send_401(): e = ApiErrors() e.addError('global', 'Authentification nécessaire') return jsonify(e.errors), 401
def date_time_cast_error(error): api_errors = ApiErrors() app.logger.warning(json.dumps(error.errors)) for field in error.errors.keys(): api_errors.add_error(field, 'Format de date invalide') return jsonify(api_errors.errors), 400