def test_update_cache_version_route(self): route1 = Route(activities=['skitouring']) route2 = Route(activities=['skitouring']) waypoint1 = Waypoint(waypoint_type='summit') waypoint2 = Waypoint(waypoint_type='summit') waypoint_unrelated = Waypoint(waypoint_type='summit') self.session.add_all( [waypoint1, waypoint2, waypoint_unrelated, route1, route2]) self.session.flush() self.session.add(Association.create(waypoint1, route1)) self.session.add(Association.create(route2, route1)) self.session.add(Association.create(waypoint2, waypoint1)) self.session.flush() update_cache_version(route1) cache_version_route1 = self.session.query(CacheVersion).get( route1.document_id) cache_version_route2 = self.session.query(CacheVersion).get( route2.document_id) cache_version_wp1 = self.session.query(CacheVersion).get( waypoint1.document_id) cache_version_wp2 = self.session.query(CacheVersion).get( waypoint2.document_id) cache_version_untouched = self.session.query(CacheVersion).get( waypoint_unrelated.document_id) self.assertEqual(cache_version_route1.version, 2) self.assertEqual(cache_version_route2.version, 2) self.assertEqual(cache_version_wp1.version, 3) self.assertEqual(cache_version_wp2.version, 2) self.assertEqual(cache_version_untouched.version, 1)
def test_update_cache_version_wp_with_associations(self): waypoint1 = Waypoint(waypoint_type='summit') waypoint2 = Waypoint(waypoint_type='summit') waypoint3 = Waypoint(waypoint_type='summit') waypoint_unrelated = Waypoint(waypoint_type='summit') self.session.add_all( [waypoint1, waypoint2, waypoint3, waypoint_unrelated]) self.session.flush() self.session.add(Association.create(waypoint1, waypoint2)) self.session.add(Association.create(waypoint3, waypoint1)) self.session.flush() update_cache_version(waypoint1) cache_version1 = self.session.query(CacheVersion).get( waypoint1.document_id) cache_version2 = self.session.query(CacheVersion).get( waypoint1.document_id) cache_version3 = self.session.query(CacheVersion).get( waypoint1.document_id) cache_version_untouched = self.session.query(CacheVersion).get( waypoint_unrelated.document_id) self.assertEqual(cache_version1.version, 2) self.assertEqual(cache_version2.version, 2) self.assertEqual(cache_version3.version, 2) self.assertEqual(cache_version_untouched.version, 1)
def test_update_cache_version_wp_as_main_wp(self): waypoint1 = Waypoint(waypoint_type='summit') waypoint2 = Waypoint(waypoint_type='summit') waypoint3 = Waypoint(waypoint_type='summit') waypoint_unrelated = Waypoint(waypoint_type='summit') route = Route(main_waypoint=waypoint1, activities=['skitouring']) self.session.add_all( [waypoint1, waypoint2, waypoint3, waypoint_unrelated, route]) self.session.flush() self.session.add(Association.create(waypoint1, route)) self.session.add(Association.create(waypoint2, route)) self.session.add(Association.create(waypoint3, waypoint2)) self.session.flush() update_cache_version(waypoint1) cache_version_wp1 = self.session.query(CacheVersion).get( waypoint1.document_id) cache_version_wp2 = self.session.query(CacheVersion).get( waypoint2.document_id) cache_version_wp3 = self.session.query(CacheVersion).get( waypoint3.document_id) cache_version_route = self.session.query(CacheVersion).get( route.document_id) cache_version_untouched = self.session.query(CacheVersion).get( waypoint_unrelated.document_id) self.assertEqual(cache_version_wp1.version, 3) self.assertEqual(cache_version_wp2.version, 2) self.assertEqual(cache_version_wp3.version, 2) self.assertEqual(cache_version_route.version, 2) self.assertEqual(cache_version_untouched.version, 1)
def test_update_cache_version_user_document_version(self): """ Test that a document is invalidated if a user name of a user that edited one of the document versions is changed. """ waypoint = Waypoint( waypoint_type='summit', elevation=2203, locales=[ WaypointLocale(lang='en', title='...', description='...')]) user_profile = UserProfile() user = User( name='test_user', username='******', email='*****@*****.**', forum_username='******', password='******', email_validated=True, profile=user_profile) self.session.add_all([waypoint, user_profile, user]) self.session.flush() DocumentRest.create_new_version(waypoint, user.id) update_cache_version(user_profile) cache_version_user_profile = self.session.query(CacheVersion).get( user_profile.document_id) cache_version_waypoint = self.session.query(CacheVersion).get( waypoint.document_id) self.assertEqual(cache_version_waypoint.version, 2) self.assertEqual(cache_version_user_profile.version, 2)
def test_update_cache_version_user_document_version(self): """ Test that a document is invalidated if a user name of a user that edited one of the document versions is changed. """ waypoint = Waypoint(waypoint_type='summit', elevation=2203, locales=[ WaypointLocale(lang='en', title='...', description='...') ]) user_profile = UserProfile() user = User(name='test_user', username='******', email='*****@*****.**', forum_username='******', password='******', email_validated=True, profile=user_profile) self.session.add_all([waypoint, user_profile, user]) self.session.flush() DocumentRest.create_new_version(waypoint, user.id) update_cache_version(user_profile) cache_version_user_profile = self.session.query(CacheVersion).get( user_profile.document_id) cache_version_waypoint = self.session.query(CacheVersion).get( waypoint.document_id) self.assertEqual(cache_version_waypoint.version, 2) self.assertEqual(cache_version_user_profile.version, 2)
def test_update_cache_version_single_wp(self): waypoint = Waypoint(waypoint_type='summit') waypoint_unrelated = Waypoint(waypoint_type='summit') self.session.add_all([waypoint, waypoint_unrelated]) self.session.flush() cache_version = self.session.query(CacheVersion).get( waypoint.document_id) cache_version.last_updated = datetime.datetime(2016, 1, 1, 12, 1, 0) self.session.flush() current_version = cache_version.version current_last_updated = cache_version.last_updated update_cache_version(waypoint) self.session.refresh(cache_version) self.assertEqual(cache_version.version, current_version + 1) self.assertNotEqual(cache_version.last_updated, current_last_updated) cache_version_untouched = self.session.query(CacheVersion).get( waypoint_unrelated.document_id) self.assertEqual(cache_version_untouched.version, 1)
def test_update_cache_version_user(self): """ Test that outings are invalidated if an user name changes. """ outing = Outing(activities=['skitouring'], date_start=datetime.date(2016, 2, 1), date_end=datetime.date(2016, 2, 1)) user_profile = UserProfile() self.session.add_all([outing, user_profile]) self.session.flush() self.session.add(Association.create(user_profile, outing)) self.session.flush() update_cache_version(user_profile) cache_version_user_profile = self.session.query(CacheVersion).get( user_profile.document_id) cache_version_outing = self.session.query(CacheVersion).get( outing.document_id) self.assertEqual(cache_version_outing.version, 2) self.assertEqual(cache_version_user_profile.version, 2)
def test_update_cache_version_outing(self): outing = Outing( activities=['skitouring'], date_start=datetime.date(2016, 2, 1), date_end=datetime.date(2016, 2, 1)) route1 = Route(activities=['skitouring']) route2 = Route(activities=['skitouring']) waypoint1 = Waypoint(waypoint_type='summit') waypoint2 = Waypoint(waypoint_type='summit') waypoint_unrelated = Waypoint(waypoint_type='summit') self.session.add_all( [outing, waypoint1, waypoint2, waypoint_unrelated, route1, route2]) self.session.flush() self.session.add(Association.create(route1, outing)) self.session.add(Association.create(waypoint1, route1)) self.session.add(Association.create(route2, outing)) self.session.add(Association.create(waypoint2, waypoint1)) self.session.flush() update_cache_version(outing) cache_version_outing = self.session.query(CacheVersion).get( outing.document_id) cache_version_route1 = self.session.query(CacheVersion).get( route1.document_id) cache_version_route2 = self.session.query(CacheVersion).get( route2.document_id) cache_version_wp1 = self.session.query(CacheVersion).get( waypoint1.document_id) cache_version_wp2 = self.session.query(CacheVersion).get( waypoint2.document_id) cache_version_untouched = self.session.query(CacheVersion).get( waypoint_unrelated.document_id) self.assertEqual(cache_version_outing.version, 2) self.assertEqual(cache_version_route1.version, 2) self.assertEqual(cache_version_route2.version, 2) self.assertEqual(cache_version_wp1.version, 2) self.assertEqual(cache_version_wp2.version, 2) self.assertEqual(cache_version_untouched.version, 1)
def test_update_cache_version_user(self): """ Test that outings are invalidated if an user name changes. """ outing = Outing( activities=['skitouring'], date_start=datetime.date(2016, 2, 1), date_end=datetime.date(2016, 2, 1)) user_profile = UserProfile() self.session.add_all([outing, user_profile]) self.session.flush() self.session.add(Association.create(user_profile, outing)) self.session.flush() update_cache_version(user_profile) cache_version_user_profile = self.session.query(CacheVersion).get( user_profile.document_id) cache_version_outing = self.session.query(CacheVersion).get( outing.document_id) self.assertEqual(cache_version_outing.version, 2) self.assertEqual(cache_version_user_profile.version, 2)
def test_update_cache_version_outing(self): outing = Outing(activities=['skitouring'], date_start=datetime.date(2016, 2, 1), date_end=datetime.date(2016, 2, 1)) route1 = Route(activities=['skitouring']) route2 = Route(activities=['skitouring']) waypoint1 = Waypoint(waypoint_type='summit') waypoint2 = Waypoint(waypoint_type='summit') waypoint_unrelated = Waypoint(waypoint_type='summit') self.session.add_all( [outing, waypoint1, waypoint2, waypoint_unrelated, route1, route2]) self.session.flush() self.session.add(Association.create(route1, outing)) self.session.add(Association.create(waypoint1, route1)) self.session.add(Association.create(route2, outing)) self.session.add(Association.create(waypoint2, waypoint1)) self.session.flush() update_cache_version(outing) cache_version_outing = self.session.query(CacheVersion).get( outing.document_id) cache_version_route1 = self.session.query(CacheVersion).get( route1.document_id) cache_version_route2 = self.session.query(CacheVersion).get( route2.document_id) cache_version_wp1 = self.session.query(CacheVersion).get( waypoint1.document_id) cache_version_wp2 = self.session.query(CacheVersion).get( waypoint2.document_id) cache_version_untouched = self.session.query(CacheVersion).get( waypoint_unrelated.document_id) self.assertEqual(cache_version_outing.version, 2) self.assertEqual(cache_version_route1.version, 2) self.assertEqual(cache_version_route2.version, 2) self.assertEqual(cache_version_wp1.version, 2) self.assertEqual(cache_version_wp2.version, 2) self.assertEqual(cache_version_untouched.version, 1)
def update_document(document, document_in, request, before_update=None, after_update=None, manage_versions=None): user_id = request.authenticated_userid # remember the current version numbers of the document old_versions = document.get_versions() if before_update: before_update(document, document_in) # update the document with the input document document.update(document_in) if manage_versions: manage_versions(document, old_versions) try: DBSession.flush() except StaleDataError: raise HTTPConflict('concurrent modification') # when flushing the session, SQLAlchemy automatically updates the # version numbers in case attributes have changed. by comparing with # the old version numbers, we can check if only figures or only locales # have changed. (update_types, changed_langs) = document.get_update_type(old_versions) if update_types: # A new version needs to be created and persisted DocumentRest.update_version(document, user_id, request.validated['message'], update_types, changed_langs) if document.type != AREA_TYPE and UpdateType.GEOM in update_types: update_areas_for_document(document, reset=True) if document.type != MAP_TYPE and UpdateType.GEOM in update_types: update_maps_for_document(document, reset=True) if after_update: after_update(document, update_types, user_id=user_id) update_cache_version(document) associations = request.validated.get('associations', None) if associations: check_association_add = \ association_permission_checker(request) check_association_remove = \ association_permission_removal_checker(request) added_associations, removed_associations = \ synchronize_associations( document, associations, user_id, check_association_add=check_association_add, check_association_remove=check_association_remove) if update_types or associations: # update search index notify_es_syncer(request.registry.queue_config) update_feed_document_update(document, user_id, update_types) if associations and (removed_associations or added_associations): update_cache_version_associations(added_associations, removed_associations) return update_types
def post(self): user = self.get_user() request = self.request validated = request.validated result = {} # Before all, check whether the user knows the current password current_password = validated['currentpassword'] if not user.validate_password(current_password): request.errors.add('body', 'currentpassword', 'Invalid password') return sync_sso = False # update password if a new password is provided if 'newpassword' in validated: user.password = validated['newpassword'] # start email validation procedure if a new email is provided email_link = None if 'email' in validated and validated['email'] != user.email: user.email_to_validate = validated['email'] user.update_validation_nonce( Purpose.change_email, VALIDATION_EXPIRE_DAYS) email_service = get_email_service(self.request) nonce = user.validation_nonce settings = request.registry.settings link = settings['mail.validate_change_email_url_template'].format( '#', nonce) email_link = link result['email'] = validated['email'] result['sent_email'] = True sync_sso = True update_search_index = False if 'name' in validated: user.name = validated['name'] result['name'] = user.name update_search_index = True sync_sso = True if 'forum_username' in validated: user.forum_username = validated['forum_username'] result['forum_username'] = user.forum_username update_search_index = True sync_sso = True if 'is_profile_public' in validated: user.is_profile_public = validated['is_profile_public'] # Synchronize everything except the new email (still stored # in the email_to_validate attribute while validation is pending). if sync_sso: try: client = get_discourse_client(request.registry.settings) client.sync_sso(user) except: log.error('Error syncing with discourse', exc_info=True) raise HTTPInternalServerError('Error with Discourse') try: DBSession.flush() except: log.warning('Error persisting user', exc_info=True) raise HTTPInternalServerError('Error persisting user') if email_link: email_service.send_change_email_confirmation(user, link) if update_search_index: # when user name changes, the search index has to be updated notify_es_syncer(self.request.registry.queue_config) # also update the cache version of the user profile update_cache_version(user.profile) return result
def post(self): user = self.get_user() request = self.request validated = request.validated result = {} # Before all, check whether the user knows the current password current_password = validated['currentpassword'] if not user.validate_password(current_password): request.errors.add('body', 'currentpassword', 'Invalid password') return sync_sso = False # update password if a new password is provided if 'newpassword' in validated: user.password = validated['newpassword'] # start email validation procedure if a new email is provided email_link = None if 'email' in validated and validated['email'] != user.email: user.email_to_validate = validated['email'] user.update_validation_nonce(Purpose.change_email, VALIDATION_EXPIRE_DAYS) email_service = get_email_service(self.request) nonce = user.validation_nonce settings = request.registry.settings link = settings['mail.validate_change_email_url_template'].format( '#', nonce) email_link = link result['email'] = validated['email'] result['sent_email'] = True sync_sso = True update_search_index = False if 'name' in validated: user.name = validated['name'] result['name'] = user.name update_search_index = True sync_sso = True if 'forum_username' in validated: user.forum_username = validated['forum_username'] result['forum_username'] = user.forum_username update_search_index = True sync_sso = True if 'is_profile_public' in validated: user.is_profile_public = validated['is_profile_public'] # Synchronize everything except the new email (still stored # in the email_to_validate attribute while validation is pending). if sync_sso: try: client = get_discourse_client(request.registry.settings) client.sync_sso(user) except Exception: log.error('Error syncing with discourse', exc_info=True) raise HTTPInternalServerError('Error with Discourse') try: DBSession.flush() except Exception: log.warning('Error persisting user', exc_info=True) raise HTTPInternalServerError('Error persisting user') if email_link: email_service.send_change_email_confirmation(user, link) if update_search_index: # when user name changes, the search index has to be updated notify_es_syncer(self.request.registry.queue_config) # also update the cache version of the user profile update_cache_version(user.profile) return result
def _put( self, clazz, schema, clazz_locale=None, before_update=None, after_update=None): user_id = self.request.authenticated_userid id = self.request.validated['id'] document_in = \ schema.objectify(self.request.validated['document']) self._check_document_id(id, document_in.document_id) # get the current version of the document document = self._get_document(clazz, id, clazz_locale=clazz_locale) if document.redirects_to: raise HTTPBadRequest('can not update merged document') if document.protected and not self.request.has_permission('moderator'): raise HTTPForbidden('No permission to change a protected document') self._check_versions(document, document_in) # remember the current version numbers of the document old_versions = document.get_versions() # update the document with the input document document.update(document_in) if before_update: before_update(document, document_in, user_id=user_id) try: DBSession.flush() except StaleDataError: raise HTTPConflict('concurrent modification') # when flushing the session, SQLAlchemy automatically updates the # version numbers in case attributes have changed. by comparing with # the old version numbers, we can check if only figures or only locales # have changed. (update_types, changed_langs) = document.get_update_type(old_versions) if update_types: # A new version needs to be created and persisted self._update_version( document, user_id, self.request.validated['message'], update_types, changed_langs) if document.type != AREA_TYPE and UpdateType.GEOM in update_types: update_areas_for_document(document, reset=True) if document.type != MAP_TYPE and UpdateType.GEOM in update_types: update_maps_for_document(document, reset=True) if after_update: after_update(document, update_types, user_id=user_id) update_cache_version(document) associations = self.request.validated.get('associations', None) if associations: check_association_add = \ association_permission_checker(self.request) check_association_remove = \ association_permission_removal_checker(self.request) added_associations, removed_associations = \ synchronize_associations( document, associations, user_id, check_association_add=check_association_add, check_association_remove=check_association_remove) if update_types or associations: # update search index notify_es_syncer(self.request.registry.queue_config) update_feed_document_update(document, user_id, update_types) if associations and (removed_associations or added_associations): update_cache_version_associations( added_associations, removed_associations) return {}
def _put( self, clazz, schema, clazz_locale=None, before_update=None, after_update=None): user_id = self.request.authenticated_userid id = self.request.validated['id'] document_in = \ schema.objectify(self.request.validated['document']) self._check_document_id(id, document_in.document_id) # get the current version of the document document = self._get_document(clazz, id, clazz_locale=clazz_locale) if document.redirects_to: raise HTTPBadRequest('can not update merged document') if document.protected and not self.request.has_permission('moderator'): raise HTTPForbidden('No permission to change a protected document') self._check_versions(document, document_in) # remember the current version numbers of the document old_versions = document.get_versions() # update the document with the input document document.update(document_in) if before_update: before_update(document, document_in, user_id=user_id) try: DBSession.flush() except StaleDataError: raise HTTPConflict('concurrent modification') # when flushing the session, SQLAlchemy automatically updates the # version numbers in case attributes have changed. by comparing with # the old version numbers, we can check if only figures or only locales # have changed. (update_types, changed_langs) = document.get_update_type(old_versions) if update_types: # A new version needs to be created and persisted DocumentRest.update_version( document, user_id, self.request.validated['message'], update_types, changed_langs) if document.type != AREA_TYPE and UpdateType.GEOM in update_types: update_areas_for_document(document, reset=True) if document.type != MAP_TYPE and UpdateType.GEOM in update_types: update_maps_for_document(document, reset=True) if after_update: after_update(document, update_types, user_id=user_id) update_cache_version(document) associations = self.request.validated.get('associations', None) if associations: check_association_add = \ association_permission_checker(self.request) check_association_remove = \ association_permission_removal_checker(self.request) added_associations, removed_associations = \ synchronize_associations( document, associations, user_id, check_association_add=check_association_add, check_association_remove=check_association_remove) if update_types or associations: # update search index notify_es_syncer(self.request.registry.queue_config) update_feed_document_update(document, user_id, update_types) if associations and (removed_associations or added_associations): update_cache_version_associations( added_associations, removed_associations) return {}