def inscription(): if request.method == 'POST': pseudo = request.form['pseudo'] email = request.form['email'] password = request.form['password'] password_confirmation = request.form['password_confirmation'] is_admin = ('admin' in request.form) existing_name = list(mongo.MongoLoad({'pseudo': pseudo}).retrieve('users_accounts')) existing_mail = list(mongo.MongoLoad({'email': email}).retrieve('users_accounts')) if existing_name: error = 'Ce pseudo est déjà utilisé, veuillez en choisir un autre.' elif existing_mail: error = 'Cette adresse mail est déjà utilisée, veuillez vous-connectez.' elif password != password_confirmation: error = 'Les deux mots de passes sont différents.' else: #Cookies session['username'] = pseudo session['admin?'] = is_admin #Cryptage du mot de passe hashpass= bcrypt.hashpw(password.encode('utf-8'),bcrypt.gensalt()) #Stockage dans mongoDB dic = { 'pseudo': pseudo, 'email': email, 'password': hashpass } if session['admin?']: dic['admin?'] = 'YES' db_tester(session['username']) #Création d'un profil testeur else: dic['admin?'] = 'NO' documents = mongo.MongoSave([dic]) documents.storeindb('users_accounts',pseudo='A',email='A') #redirection vers la map return redirect(url_for('map')) return render_template('inscription.html',error=error) elif 'username' in session: return redirect(url_for('map')) else: return render_template('inscription.html')
def get_list_version(): """Crée la collection de stockage des versions du scraper si elle n'existe pas. Récupère les résultats de tests finaux et les traite pour générer de nouvelles règles. Une nouvelle version du scraper est alors créée pour utiliser ces règles. Toutes les versions sont envoyées à la page de lancement du scrape et sont proposées à l'utilisateur. """ dbfinder = mongo.MongoLoad(proj={'search_version': 1, '_id': 0}) if not dbfinder.mongocheck('Versions_Scrape'): version_doc = mongo.MongoSave([{ 'search_version': '1.00', 'submissions_scraped': 0, 'accuracy': 0 }]) version_doc.storeindb('Versions_Scrape', search_version='D') proc.create_rule() version_list = [] for doc in dbfinder.retrieve('Versions_Scrape'): version_list.append(doc['search_version']) return jsonify(version_list)
def get_count(): """Renvoie le nombre total de documents que le testeur en session doit tester, et les versions du scraper disponibles. """ dbcounter = mongo.MongoLoad({'user_id': session['username']}, { 'code': 1, '_id': 0 }) test_code = next(dbcounter.retrieve('Testeurs'))['code'] doc_number = dbcounter.mongocount( 'Resultats_RGN', {'testers': { '$bitsAllSet': 2**test_code }}) version_list = [] dbcounter.reinit({'testers': { '$bitsAllSet': 2**test_code }}, { 'search_version': 1, '_id': 0 }) for doc in dbcounter.retrieve('Resultats_RGN'): if doc['search_version'] not in version_list: version_list.append(doc['search_version']) return jsonify(nbtest=doc_number, pseudo=session['username'], versions=version_list)
def connexion(): if request.method == 'POST': pseudo_email = request.form['pseudo_email'] password = request.form['password'] #Chercher le compte en supposant que c'est le pseudo cmpt = mongo.MongoLoad({'pseudo': pseudo_email}) compte = list(cmpt.retrieve('users_accounts',limit=1)) #Si compte pas trouvé, chercher le compte en supposant que c'est le mail if not compte: cmpt.reinit({'email': pseudo_email}) compte = list(cmpt.retrieve('users_accounts',limit=1)) #Si compte trouvé if compte: compte = compte[0] #Vérifier le mot de passe checkpw(password, hashed) if bcrypt.hashpw(password.encode('utf-8'),compte['password']) == compte['password']: #Cookies session['username'] = compte['pseudo'] session['admin?'] = ( compte['admin?'] == "YES" ) return redirect(url_for('map')) #Pseudo,email ou mot de passe invalide error = 'Le pseudo/email ou le mot de passe n\'est pas valide' return render_template('connexion.html',error=error) elif 'username' in session: return redirect(url_for('map')) else: return render_template('connexion.html')
def select_results(version, url_list): """Prépare le traitement des résultats de tests des documents dont tous les tests ont été effectués. Récupère les résultats dans la collection de résultats et les valeurs nécessaires dans les documents issus du scraping. Sélection des résultats de test définitifs à partir des choix majoritaires des testeurs. """ dbfinder = mongo.MongoLoad( { 'img_url': { '$in': url_list }, 'search_version': version }, { 'img_url': 1, 'locations_selected': 1, 'sufficient': 1, '_id': 0 }) group_results = bucket(dbfinder.retrieve('Resultats_Test_Expert_1'), key=lambda x: x['img_url']) dbfinder.reinit({ 'img_url': { '$in': url_list }, 'search_version': version }, { 'search_version': 1, 'country': 1, 'img_url': 1, 'tag_list': 1, 'location_list': 1, '_id': 0 }) final_results = [] for doc in dbfinder.retrieve('Resultats_RGN'): result = list( group_results[doc['img_url']] ) #Conversion de l'itérateur car double parcours nécessaire doc['locations_selected'] = [ True if comp.count(True) > len(comp) / 2 else False for comp in zip(*[res['locations_selected'] for res in result]) ] doc['sufficient'] = True if sum( 1 if b else -1 for b in [res['sufficient'] for res in result]) > 0 else False doc['processed'] = False final_results.append(doc) return final_results
def get_results(): """Extraction de documents à tester de la collection 'Résultats_RGN' (résultats du scraping) de la base de données, en fonction du testeur qui a lancé la requête, et renvoie en format JSON des résultats à la page de tests. """ result_value = request.args.get('value') version = request.args.get('version') limit = int(request.args.get('limit')) dbfinder = mongo.MongoLoad({'user_id': session['username']}, { 'code': 1, '_id': 0 }) test_code = next(dbfinder.retrieve('Testeurs'))['code'] dbfinder.reinit( { 'search_version': version, 'test_result': result_value, 'testers': { '$bitsAllSet': 2**test_code } }, #Opérateur binaire { 'text': 1, 'search_version': 1, 'img_url': 1, 'tag_list': 1, 'scraped_title': 1, 'test_list': 1, 'location_list': 1, 'location': 1, 'name': 1, 'country_code': 1, '_id': 0 }) doc_list = list(dbfinder.retrieve('Resultats_RGN', limit=limit)) print(doc_list[0]) return jsonify(results=doc_list)
def location_finder(country, version, tags): """Recherche des lieux potentiels dans le titre de la soumission Reddit. La recherche de base considère que les noms propres voisins forment un lieu. Par-dessus cette recherche, les règles générées par l'analyse des résultats des tests utilisateurs fournissent des mots particuliers à prendre en compte (dotés de l'étiquette 'TAR') ou à ignorer (l'étiquette 'IGN'). En plus des lieux potentiels, la liste résultat stocke le nombre de mots non sélectionnés séparant les lieux retenus. """ db = mongo.MongoLoad( { 'country': country, 'search_version': { '$lte': version } }, { 'expr': 1, 'pos': 1, 'take': 1, '_id': 0 }) for rule in db.retrieve('Nouvelles_Regles'): expr_len = len(rule['pos']) expr_str = '' expr_tags = [] for i in range(len(tags)): if i - expr_len + 1 >= 0: expr_str = ' '.join(tags[i][0] for i in range(i - expr_len + 1, i + 1)) expr_tags = [ tags[i][1] for i in range(i - expr_len + 1, i + 1) ] if expr_str == rule['expr']: if all(expr_tags[i] == rule['pos'][i] for i in range(expr_len)): left = i - expr_len if rule['take'] == 0: for j in range(i - expr_len, i + 1): if tags[j][1] != 'NP0': tags[j] = (tags[j][0], 'TAR', tags[j][2]) elif rule['take'] == 'X': for j in range(i - expr_len, i + 1): tags[j] = (tags[j][0], 'IGN', tags[j][2]) elif rule['take'] == 1: if i + 1 < len(tags) and tags[i + 1][1] != 'NP0': tags[i + 1] = (tags[i + 1][0], 'TAR', tags[i + 1][2]) elif rule['take'] == 'R': if i + 1 < len(tags): tags[i + 1] = (tags[i + 1][0], 'IGN', tags[i + 1][2]) elif rule['take'] == -1: if left >= 0 and tags[left][1] != 'NP0': tags[left] = (tags[left][0], 'TAR', tags[left][2]) elif rule['take'] == 'L': if left >= 0: tags[left] = (tags[left][0], 'IGN', tags[left][2]) else: if i+1 < len(tags) and tags[i+1][1] == 'NP0' \ and left >= 0 and tags[left][1] == 'NP0': tags[i] = (tags[i][0], 'TAR', tags[i][2]) loc_list = [ ' '.join(t[0] for t in g) if k else len(list(g)) for k, g in groupby(tags, key=lambda x: x[1] == 'NP0' or x[1] == 'TAR') ] return loc_list
def scraping(): """Si l'utilisateur n'a pas demandé un scraping, recherche de documents du pays sélectionné dans la base de données; ces documents et leurs liens vers les photos seront renvoyés. Si l'utilisateur a demandé un scraping, ou s'il n'y a pas ou pas assez de documents du pays sélectionné dans la base de données, configuration et lancement du scrape sur Reddit, puis étiquetage des titres des soumissions résultats par TreeTagger, et analyse des étiquettes pour obtenir une liste de lieux potentiels. Ces lieux sont recherchés sur geonames. Les résultats de cette dernière recherche sont chargés dans deux dictionnaires, l'un pour l'affichage des photos sur le site et l'autre pour stocker les résultats dans la base de données sur mongoDB. NB: le scraping tente toujours d'obtenir de nouvelles photos (absentes de mongoDB). """ #Configuration Geoscape geokey = current_app.config['GEOKEY'] geoauth = current_app.config['GEOAUTH'] #Paramètres de la requête Javascript rgnversion = request.args.get('search_version') country = request.args.get('country') country_code = request.args.get('country_code') limit = int(request.args.get('nombre_image')) scrape_requested = True if request.args.get( 'scraping') == 'true' else False #Dico de résultats pour l'affichage sur le site search_res = geo.GeoQuery(geokey, geoauth, country, country_code, 'E') dic_results = { 'head': { 'total': 0, 'country': { 'name': country, 'lng': search_res.result.lng, 'lat': search_res.result.lat } }, 'results': [] } #Liste de chargement pour la base de données database_list = [] if scrape_requested: #On ne charge que les img_url load_arg = {'img_url': 1, '_id': 0} else: #On charge le document pour l'affichage load_arg = { 'scraped_title': 0, 'location_list': 0, 'feature_class': 0, 'testers': 0, '_id': 0 } existing_urls = [] check_db = mongo.Mongo.mongocheck('Resultats_RGN') #Initialisation de la collection des résultats si elle n'existe pas if not check_db: dbstart = mongo.MongoSave([{ 'key': 'Initialisation de la collection Resultats_RGN.' }]) dbstart.storeindb('Resultats_RGN', img_url='A', search_version='D') dbstart.nonunique_index('Resultats_RGN', country='A', search_version='D') #Les documents pris dans la base de données sont chargés dans le dictionnaire de résultats else: dbfinder = mongo.MongoLoad( { 'search_version': rgnversion, 'country': country }, load_arg) for doc in dbfinder.retrieve('Resultats_RGN', limit=limit): if not scrape_requested: dic_results['head']['total'] += 1 dic_results['results'].append(doc) existing_urls.append('-url:' + doc['img_url']) if scrape_requested or dic_results['head']['total'] < limit: #Configuration recherche reddit, profil chargé depuis praw.ini reddit = praw.Reddit('current_user') target_sub = reddit.subreddit('EarthPorn') query = country if country != 'United States' else 'USA' print( '\033[92m' + target_sub.display_name + '\033[0m' '\nRésultats de recherche pour les soumissions reddit avec:', query, '\n') #Exclure les documents déjà récupérés user_limit = limit if len(query) + len(existing_urls) + sum( len(url) for url in existing_urls) <= 512: query += (' ' + ' '.join(existing_urls)).rstrip() limit -= dic_results['head']['total'] else: #512 caractères max dans une requête Reddit limit = 1000 #Max permis par Reddit existing_urls = [url[5:] for url in existing_urls] #Config TreeTagger. Le dossier Treetagger doit être dans le dossier d'où le programme est exécuté if sys.platform.startswith('linux'): reddit_tagger = TreeTagger(TAGLANG='en', TAGDIR=join(getcwd(), 'Treetagger', 'TreeTagger_unix')) elif sys.platform.startswith('win'): reddit_tagger = TreeTagger(TAGLANG='en', TAGDIR=join(getcwd(), 'Treetagger', 'TreeTagger_windows')) else: sys.exit('Système d\'exploitation non compatible avec Geoscape.') #Résultats de la recherche dans le subreddit test_posts = target_sub.search(query, limit=limit) for post in test_posts: try: attempt = post.url except prawcore.exceptions.NotFound: continue #Problème avec la photo; éliminé if post.url in existing_urls: continue #Déjà stocké dans la base de données; éliminé if search('\W' + country + '\W', post.title): #Pays comme mot distinct #Saute aux plus une fois des caractères entre [] ou () au début du texte et s'arrête au premier [ ou ( res = search('^(?:[\[(].*[\])])?([^\[(]+)', post.title) if (res): print(res.group(1)) #Tagging: génère une liste de triplets: (word=..., pos=..., lemma=...) reddit_tags = make_tags(reddit_tagger.tag_text( res.group(1)), exclude_nottags=True) #Le nom du pays est exclu des lieux potentiels; rajouté seulement en dernier recours country_split = country.casefold().split(' ') size = len(country_split) indexes = [] if size > 1: name_tags = [t[0].casefold() for t in reddit_tags] for window in enumerate(windowed(name_tags, size)): if all(window[1][i] == country_split[i] for i in range(size)): indexes.extend([ i for i in range(window[0], window[0] + size) ]) for index, tag in enumerate(reddit_tags): if tag[1] == 'NP': #Tag nom propre sous Windows reddit_tags[index] = (tag[0], 'NP0', tag[2]) if tag[0].casefold() == country.casefold( ) or index in indexes: reddit_tags[index] = (tag[0], 'CTY', tag[2]) pprint(reddit_tags) #Recherche des lieux potentiels, avec stocké entre les lieux le nombre de mots non choisis location_list = location_finder(country, rgnversion, reddit_tags) print('Lieux trouvés:', end='') print(location_list, '\n') #Geonames date = gmtime(post.created_utc) dic_mongo = { 'link': 'https://www.reddit.com' + post.permalink, 'img_url': post.url, 'search_version': rgnversion, 'country': country, 'country_code': country_code, 'scraped_title': res.group(1).strip(), 'text': post.title, 'tag_list': reddit_tags, 'location_list': location_list, 'date': { 'year': date.tm_year, 'month': date.tm_mon, 'day': date.tm_mday, 'hour': date.tm_hour, 'min': date.tm_min, 'sec': date.tm_sec } } try: attempt = post.author.icon_img except prawcore.exceptions.NotFound: pass else: dic_mongo['author'] = { 'name': post.author.name, 'icon': post.author.icon_img, 'profile': 'https://www.reddit.com/user/' + post.author.name } """ R: recherche standard RF: recherche fuzzy E: recherche exacte EH: recherche exacte sur ensembles humains EN: recherche exacte sur ensembles naturels """ placefinder = geo.LocationList(country_code, location_list) geo_res = placefinder.geo_search(geokey, geoauth, 'EN EH', 'R') #Objet GeoQuery #En dernier recours, le pays lui-même s'il est dans le titre if geo_res.result is None and country in res.group(1): placefinder.reinit(country_code, [country]) geo_res = placefinder.geo_search(geokey, geoauth, 'E') if geo_res.result is not None: dic_results['head']['total'] += 1 print('Résultat GeoNames:', geo_res.result.address, end='') print('. Après', placefinder.counter, 'requêtes.') dic_mongo['name'] = geo_res.result.address #Nom dic_mongo['lng'] = geo_res.result.lng dic_mongo['lat'] = geo_res.result.lat dic_mongo[ 'feature_class'] = geo_res.result.feature_class dic_mongo['location'] = geo_res.location dic_results['results'].append(dic_mongo) dic_tostore = deepcopy(dic_mongo) database_list.append(dic_tostore) user_limit -= 1 if not user_limit: break print('\n###############') #Chargement dans la base de données des documents générés par le scrape documents = mongo.MongoSave(database_list) documents.storeindb('Resultats_RGN') return jsonify(dic_results)
def report(): """Déclenchée par le signalement d'une image mal affichée sur la carte (mauvais lieu choisi). L'information est rajoutée au document dans la base de données, ce qui le rend disponible pour le test avancé. Sélection aléatoire de 3 testeurs à partir de la collection 'Testeurs', et stockage de leurs codes comme champ du document signalé. Ce champ 'testers' est de type BSON BinData: une chaîne en base 64 représentant un champ de bits de taille quelconque, ce qui permet de stocker des valeurs supérieures à 2^63-1. Le module pymongo convertit automatiquement un objet de type bytes en BinData. """ #La méthode POST renvoie des bytes: convertir en string JSON puis en dico python data = json.loads(request.data.decode('utf-8')) response = data['image'] value = data['value'] #OK ou NOT_OK word_list = data['list_words'] #Vide si OK dbfinder = mongo.MongoLoad( { 'search_version': response['search_version'], 'img_url': response['img_url'], 'test_result': { '$exists': True } }, { 'img_url': 1, '_id': 0 }) try: next(dbfinder.retrieve('Resultats_RGN')) except StopIteration: #L'image n'a pas été signalée auparavant update = mongo.MongoUpd( { 'img_url': response['img_url'], 'search_version': response['search_version'] }, {'$set': { 'test_result': value, 'test_list': word_list }}) update.singleval_upd('Resultats_RGN') dbfinder.reinit(proj={'code': 1, '_id': 0}) tester_list = list(dbfinder.retrieve('Testeurs')) n = len(tester_list) if n >= 1: num_testers = min(n if n % 2 else n - 1, 9) seed() #Sélection aléatoire dans la liste tester_sum = sum(2**t['code'] for t in sample(tester_list, num_testers)) bytesize = floor(log2(tester_sum) / 8 if tester_sum else 0) + 1 #log2(0) non défini tester_sum = tester_sum.to_bytes(bytesize, byteorder='big') update.reinit(update={'$set': {'testers': tester_sum}}) update.singleval_upd('Resultats_RGN') return jsonify(status='OK')
def send_results(): """Réception des résultats du test-expert et stockage dans la base de données; décrémentation de la champ 'testers' des documents qui viennent d'être testés de la collection 'Resultats_RGN', et incrémentation du champ 'num_answers' du testeur dans la collection 'Testeurs'. Si tous les tests sur un document ont été réalisés, lance la sélection des résultats finaux à partir des choix des testeurs, puis stocke ce résultat final dans la collection 'Resultats_Final_Expert_1'. """ response = json.loads(request.data.decode('utf-8')) tester = session['username'] version = response['search_version'] test_results = response['results'] url_list = response['img_url'] result_docs = [] for url, test_item in zip(url_list, test_results): if test_item[ 'lieux_choisis']: #Si la liste est vide, le testeur n'a pas su répondre result_docs.append({ 'tester': tester, 'search_version': version, 'img_url': url, 'locations_selected': test_item['lieux_choisis'], 'sufficient': test_item['suffisant'], 'geoname': test_item['geonames_chosen_result'] }) documents = mongo.MongoSave(result_docs) documents.storeindb('Resultats_Test_Expert_1', tester='A', img_url='A', search_version='D') update = mongo.MongoUpd({'user_id': tester}, {'$inc': {'num_answers': 1}}) update.singleval_upd('Testeurs') dbfinder = mongo.MongoLoad({'user_id': tester}, {'code': 1, '_id': 0}) test_code = next(dbfinder.retrieve('Testeurs'))['code'] dbfinder.reinit({ 'img_url': { '$in': url_list }, 'search_version': version }, { 'testers': 1, '_id': 0 }) sum_list = [] done_list = [] for url, doc in zip(url_list, dbfinder.retrieve('Resultats_RGN')): tester_sum = int.from_bytes( doc['testers'], byteorder='big') #classmethod, appelée sans instance tester_sum &= (~(1 << test_code)) if tester_sum == 0: done_list.append(url) bytesize = 1 else: bytesize = floor(log2(tester_sum) / 8) + 1 tester_sum = tester_sum.to_bytes(bytesize, byteorder='big') sum_list.append(tester_sum) update.reinit({ 'img_url': '', 'search_version': version }, {'$set': { 'testers': '' }}, url_list, sum_list) update.multval_upd('Resultats_RGN', 'img_url') if done_list: final_list = proc.select_results(version, done_list) documents.reinit(final_list) documents.nonunique_index('Resultats_Final_Expert_1', processed='A') documents.storeindb('Resultats_Final_Expert_1', img_url='A', search_version='D') return jsonify(status='OK')
def create_rule(): """Recherche d'erreurs et création de nouvelles règles à partir de celles-ci. Récupère dans la base de données les résultats finaux des tests si le champ 'sufficient' est vrai, c'est-à-dire s'il est possible de récupérer le lieu dans le titre. La liste des lieux potentiels est converti en liste de mots distincts correctement indiciés. Cette liste est ensuite fusionnée avec les listes de résultat du test et d'étiquettes TreeTagger. Sont extraites de cette liste de comparaison les sous-listes contenant les erreurs détectées et les noms propres voisins. Les nouvelles règles sont construites à partir de ces sous-listes. """ dbfinder = mongo.MongoLoad(proj={'search_version': 1, '_id': 0}) max_version = max(doc['search_version'] for doc in dbfinder.retrieve('Versions_Scrape')) next_version = str(float(max_version) + 0.01) dbfinder.reinit({ 'processed': { '$eq': False }, 'sufficient': { '$eq': True } }, { 'search_version': 0, '_id': 0 }) rule_list = [] for doc in dbfinder.retrieve('Resultats_Final_Expert_1'): words = chain( *(loc.split(' ') if type(loc) == str else [0 for i in range(loc)] for loc in doc['location_list'])) tags = doc['tag_list'] comp_list = list( zip(doc['locations_selected'], words, (t[1] for t in tags), (t[0] for t in tags))) bad_results = [] good_neighbors = [] err = [] i = 0 while i < len(comp_list): if comp_list[i][0] and comp_list[i][0] == bool(comp_list[i][1]): good_neighbors.append( list(takewhile(lambda x: x[0], comp_list[i:]))) i += len(good_neighbors) if i < len(comp_list) and comp_list[i][0] != bool(comp_list[i][1]): errpos = len(good_neighbors) errtype = comp_list[i][0] err = list( takewhile(lambda x: x[0] != bool(x[1]) and x[0] == errtype, comp_list[i:])) badlen = len(err) err.extend( list(takewhile(lambda x: x[0], comp_list[i + badlen:]))) good_neighbors.extend(err) bad_results.append({ 'errpos': errpos, 'errlen': badlen, 'errlist': good_neighbors, 'errtype': errtype }) good_neighbors = [] err = [] i += badlen else: good_neighbors.clear() i += 1 for res in bad_results: errpos = res['errpos'] errlen = res['errlen'] errlist = res['errlist'] errtype = res['errtype'] rule_vect = errlist[errpos:errpos + errlen] take = 0 if errtype else 'X' if len(errlist) - errlen > 0: take = 2 if errtype else 'X' if errpos == len(errlist) - 1: rule_vect = errlist[:errpos] take = 1 if errtype else 'R' elif errpos == 0: rule_vect = errlist[errpos + errlen:] take = -1 if errtype else 'L' new_rule = { 'country': doc['country'], 'expr': ' '.join(i[3] for i in rule_vect), 'pos': [i[2] for i in rule_vect], 'take': take, 'search_version': next_version, 'img_url': doc['img_url'], 'verified': False } rule_list.append(new_rule) if rule_list: db = mongo.MongoSave(rule_list) db.nonunique_index('Nouvelles_Regles', country='A', search_version='D') db.storeindb('Nouvelles_Regles', country='A', expr='A', pos='A', take='A') db.reinit([{ 'search_version': next_version, 'submissions_scraped': 0, 'accuracy': 0 }]) db.storeindb('Versions_Scrape') upd = mongo.MongoUpd({'processed': { '$eq': False }}, {'$set': { 'processed': True }}) upd.singleval_upd('Resultats_Final_Expert_1')