def sub_from_field(self, field_name: str, value): if field_name == 'follows': odm.find('follower').eq('follower', self).eq('follows', value).delete() elif field_name == 'blocked_users': odm.find('blocked_user').eq('blocker', self).eq('blocked', value).delete() else: self._entity.f_sub(field_name, value) return self
def find(model: str, **kwargs) -> odm.SingleModelFinder: """Instantiate content entities finder """ check_publish_time = kwargs.get('check_publish_time', True) language = kwargs.get('language', lang.get_current()) status = kwargs.get('status', [CONTENT_STATUS_PUBLISHED]) if not is_model_registered(model): raise KeyError(f"Model '{model}' is not registered as content model") f = odm.find(model) mock = f.mock # type: Content # Publish time if mock.has_field('publish_time'): f.sort([('publish_time', odm.I_DESC)]) if check_publish_time: f.lte('publish_time', datetime.now()).no_cache('publish_time') else: f.sort([('_modified', odm.I_DESC)]) # Language if language != '*' and mock.has_field('language'): f.eq('language', language) # Status if status != '*' and mock.has_field('status'): if isinstance(status, str): status = [status] elif not isinstance(status, (list, tuple)): raise TypeError(f"'status' must be a string, list or tuple, not {type(status)}") f.inc('status', status) return f
def get(self, uid: str) -> _file.model.AbstractFile: """Get file by UID """ if not isinstance(uid, str): raise _file.error.InvalidFileUidFormat('Invalid file UID format: {}.'.format(uid)) # This driver uses UIDs in form 'model:entity_uid' uid_split = uid.split(':') if len(uid_split) != 2 or not _odm.is_model_registered(uid_split[0]): raise _file.error.InvalidFileUidFormat('Invalid file UID format: {}.'.format(uid)) # Search fo ODM entity in appropriate collection try: odm_entity = _odm.find(uid_split[0]).eq('_id', uid_split[1]).first() except _bson_errors.InvalidId: raise _file.error.FileNotFound('ODM entity is not found for file {}'.format(uid)) if not odm_entity: raise _file.error.FileNotFound('ODM entity is not found for file {}'.format(uid)) # Select corresponding file model if isinstance(odm_entity, _model.ImageFileODMEntity): return _model.ImageFile(odm_entity) elif isinstance(odm_entity, _model.AnyFileODMEntity): return _model.AnyFile(odm_entity) else: raise TypeError('Unknown file type')
def delete_comment(self, uid: str): """Mark comment as deleted """ comment = odm.find('comment').eq('_id', uid).first() if not comment: raise comments.error.CommentNotExist( "Comment '{}' does not exist.".format(uid)) comment.f_set('status', 'deleted').save()
def _on_user_pre_delete(user: auth.AbstractUser): if mock.has_field('author'): e = odm.find(model).eq('author', user).first() if e: raise errors.ForbidDeletion( lang.t('odm_auth@forbid_user_deletion', { 'user': user.login, 'entity': e, }))
def find_roles(self, query: query.Query = None, sort: List[Tuple[str, int]] = None, limit: int = None, skip: int = 0) -> Iterator[auth.AbstractRole]: """Find roles """ # Return generator return (self._role_cls(role_entity) for role_entity in odm.find( 'role', query=query).skip(skip).get(limit))
def get_comment(self, uid: str) -> comments.model.AbstractComment: """Get single comment """ comment = odm.find('comment').eq('_id', uid).first() if not comment: raise comments.error.CommentNotExist( "Comment '{}' not exist.".format(uid)) return _model.Comment(comment)
def _get_finder(self) -> odm.Finder: f = odm.find(self._model) if self._sort_field: f.sort([(self._sort_field, self._sort_order)]) if self._finder_adjust: self._finder_adjust(f) return f
def find(content_language: str = None) -> _odm.Finder: """Get ODM finder for 'content_import' model. """ f = _odm.find('content_import') if content_language is None: f.eq('content_language', _lang.get_current()) elif content_language != '*': f.eq('content_language', content_language) return f
def get_field(self, field_name: str, **kwargs): if field_name == 'follows': f = odm.find('follower').eq('follower', self) return [ f.follows for f in f.skip(kwargs.get('skip', 0)).get( kwargs.get('count', 10)) ] if field_name == 'follows_count': return odm.find('follower').eq('follower', self).count() elif field_name == 'followers': return [ f.follower for f in odm.find('follower').eq('follows', self).get() ] elif field_name == 'followers_count': return odm.find('follower').eq('follows', self).count() elif field_name == 'blocked_users': return [ b.blocked for b in odm.find('blocked_user').eq('blocker', self).get() ] elif field_name == 'blocked_users_count': return odm.find('blocked_user').eq('blocker', self).count() return self._entity.f_get(field_name, **kwargs)
def exec(self) -> _http.JSONResponse: try: model = self.args['model'] skip = self.args['skip'] limit = self.args['limit'] rule_name = self.args.pop('_pytsite_http_api_rule_name') cls = _check_api_enabled(_odm.get_model_class(model)) finder = _odm.find(model) if 'refs' in self.args: finder.inc('_ref', self.args['refs']) if 'exclude' in self.args: finder.ninc('_ref', self.args['exclude']) cls.odm_http_api_get_entities(finder, self.args) # Prepare pagination calculations total = finder.count() link_args = _deepcopy(self.args) links = [] # Link to first page link_args['skip'] = 0 links.append('<{}>; rel="first"'.format(_urlquote(_http_api.url(rule_name, link_args)))) # Link to last page link_args['skip'] = total - limit if link_args['skip'] < 0: link_args['skip'] = 0 links.append('<{}>; rel="last"'.format(_urlquote(_http_api.url(rule_name, link_args)))) # Link to previous page prev_skip = skip - limit if prev_skip >= 0: link_args['skip'] = prev_skip links.append('<{}>; rel="prev"'.format(_urlquote(_http_api.url(rule_name, link_args)))) # Link to next page next_skip = skip + limit if next_skip < total: link_args['skip'] = next_skip links.append('<{}>; rel="next"'.format(_urlquote(_http_api.url(rule_name, link_args)))) r = [] for entity in finder.skip(skip).get(limit): r.append(entity.odm_http_api_get_entity(self.args)) return _http.JSONResponse(r, 200, _http.Headers({'Link': ','.join(links)})) except _odm.error.ModelNotRegistered as e: raise self.not_found(e)
def get_comments( self, thread_uid: str, limit: int = 0, skip: int = 0) -> Iterable[comments.model.AbstractComment]: """Get comments tree """ f = odm.find('comment') \ .eq('thread_uid', thread_uid) \ .eq('_parent', None) \ .sort([('publish_time', odm.I_ASC)]) \ .skip(skip) for e in f.get(limit): yield _model.Comment(e)
def get_role(self, name: str = None, uid: str = None) -> auth.AbstractRole: f = odm.find('role') if name: f.eq('name', name) elif uid: f.eq('uid', uid) else: raise RuntimeError("Either role's name or UID must be specified") role_entity = f.first() # type: _model.ODMRole if not role_entity: raise auth.error.RoleNotFound(name) return self._role_cls(role_entity)
def _get(self, key: str) -> Any: """Get setting's value """ key = key.split('.') key_split_len = len(key) if key_split_len > 2: raise RuntimeError( 'No more than one dot is currently supported by this registry driver: {}' .format(key)) entity = odm.find('setting').eq('uid', key[0]).first() if not entity: return setting_value = dict(entity.f_get('value')) return setting_value.get( key[1]) if key_split_len == 2 else setting_value
def find(model: str, language: str = None): """Get finder for the taxonomy model """ if not is_model_registered(model): raise RuntimeError( "Model '{}' is not registered as taxonomy model.".format(model)) f = odm.find(model) if f.mock.has_field('weight'): f.sort([('weight', odm.I_DESC)]) elif f.mock.has_field('order'): f.sort([('order', odm.I_ASC)]) if not language: f.eq('language', lang.get_current()) elif language != '*': f.eq('language', language) return f
def exec(self) -> dict: lng = _lang.get_current() email = _validation.rule.Email(value=self.arg('email')).validate() # Search for subscriber s = _odm.find('content_digest_subscriber').eq('email', email).eq( 'language', lng).first() # Create new subscriber if not s: s = _odm.dispense('content_digest_subscriber').f_set( 'email', email).f_set('language', lng).save() # Enable subscriber s.f_set('enabled', True).save() return { '__alert': _lang.t('content_digest@digest_subscription_success'), '__reset': True, }
def find_users(self, query: query.Query = None, sort: List[Tuple[str, int]] = None, limit: int = None, skip: int = 0) -> Iterator[auth.AbstractUser]: """Find users """ f = odm.find('user', query=query).skip(skip) if sort: for sort_field, sort_order in sort: if sort_field in ('created', 'modified'): sort_field = '_' + sort_field elif sort_field == 'full_name': sort_field = 'first_name' elif sort_field == 'is_online': sort_field = 'last_activity' f.sort([(sort_field, sort_order)]) # Return generator return (self._user_cls(user_entity) for user_entity in f.get(limit))
def get_user(self, login: str = None, nickname: str = None, uid: str = None) -> auth.AbstractUser: # Don't cache finder results due to frequent user updates in database f = odm.find('user').cache(0) if login is not None: f.eq('login', login) elif nickname is not None: f.eq('nickname', nickname) elif uid is not None: f.eq('uid', uid) else: raise RuntimeError('User search criteria was not specified') user_entity = f.first() # type: _model.ODMUser if not user_entity: # Hide exception details to logs logger.warn("User not exist: login={}, nickname={}, uid={}".format( login, nickname, uid)) raise auth.error.UserNotFound() return self._user_cls(user_entity)
def _put(self, key: str, value: Any): """Set setting's value """ key_split = key.split('.') key_split_len = len(key_split) if key_split_len > 2: raise RuntimeError( 'No more than one dot is currently supported by this registry driver: {}' .format(key)) entity = odm.find('setting').eq('uid', key_split[0]).first() if not entity: entity = odm.dispense('setting').f_set('uid', key_split[0]) if key_split_len == 2: stored_value = dict(entity.f_get('value')) stored_value[key_split[1]] = value entity.f_set('value', stored_value) else: entity.f_set('value', value) entity.save()
def cron_1min(): """pytsite.cron.1min """ global _working if _working: _logger.warn('Content import is still working') return _working = True max_errors = _reg.get('content_import.max_errors', 13) max_items = _reg.get('content_import.max_items', 10) delay_errors = _reg.get('content_import.delay_errors', 120) importer_finder = _odm.find('content_import') \ .eq('enabled', True) \ .lt('paused_till', _datetime.now()) \ .sort([('errors', _odm.I_ASC)]) for importer in importer_finder.get(): # type: _model.ContentImport options = dict(importer.driver_opts) options.update({ 'content_author': importer.content_author, 'content_model': importer.content_model, 'content_language': importer.content_language, 'content_status': importer.content_status, 'content_section': importer.content_section, }) driver = _api.get_driver(importer.driver) items_imported = 0 try: _logger.info('Content import started. Driver: {}. Options: {}'.format(driver.get_name(), options)) # Get entities from driver and save them for entity in driver.get_entities(_frozendict(options)): if items_imported == max_items: break try: # Append additional tags if entity.has_field('tags'): for tag_title in importer.add_tags: tag = _tag.find_by_title(tag_title, language=importer.content_language) if not tag: tag = _tag.dispense(tag_title, language=importer.content_language).save() entity.f_add('tags', tag) # Save entity entity.save() # Notify listeners _events.fire('content_import@import', driver=driver, entity=entity) _logger.info("Content entity imported: '{}'".format(entity.f_get('title'))) items_imported += 1 # Entity was not successfully saved; make record in the log and skip to the next entity except Exception as e: # Delete already attached images to free space if entity.has_field('images') and entity.images: for img in entity.images: img.delete() _logger.error("Error while creating entity '{}'. {}".format(entity.title, str(e)), exc_info=e) # Mark that driver made its work without errors importer.f_set('errors', 0) _logger.info('Content import finished. Entities imported: {}.'.format(items_imported)) except Exception as e: # Increment errors counter importer.f_inc('errors') # Store info about error importer.f_set('last_error', str(e)) if importer.errors >= max_errors: # Disable if maximum errors count reached importer.f_set('enabled', False) else: # Pause importer importer.f_set('paused_till', _datetime.now() + _timedelta(minutes=delay_errors)) _logger.error(e) # Continue to the next importer continue finally: importer.save() _working = False
def is_followed(self, user_to_check: auth.model.AbstractUser) -> bool: return bool( odm.find('follower').eq('follower', user_to_check).eq('follows', self).count())
def is_blocks(self, user_to_check: auth.model.AbstractUser) -> bool: return bool( odm.find('blocked_user').eq('blocker', self).eq('blocked', user_to_check).count())
def count_users(self, query: query.Query = None) -> int: return odm.find('user', query=query).count()
def get_comments_count(self, thread_uid: str) -> int: """Get comments count for particular thread """ return odm.find('comment').eq('thread_uid', thread_uid).eq('status', 'published').count()
def count_roles(self, query: query.Query = None) -> int: return odm.find('role', query=query).count()
def delete_thread(self, thread_uid: str): """Remove comments for particular thread """ return odm.find('comment').eq('thread_uid', thread_uid).delete(True)
def on_cron_every_min(): """Send weekly mail digest """ # Check if the models is specified models = _reg.get('content_digest.models') if not models: return # Check for the current day and time weekdays = _reg.get('content_digest.days_of_week', []) # type: list time_of_day = _reg.get('content_digest.day_time', '00:00') if isinstance(time_of_day, _datetime): time_of_day = time_of_day.time() else: time_of_day = _util.parse_date_time(time_of_day).time() now = _datetime.now() now_weekday = now.weekday() if now.weekday() not in weekdays or not (time_of_day.hour == now.hour and time_of_day.minute == now.minute): return # Calculate days number to query collections prev_weekday = weekdays[weekdays.index(now_weekday) - 1] if prev_weekday < now_weekday: days_diff = (now_weekday + 1) - (prev_weekday + 1) else: days_diff = 8 - (prev_weekday + 1) + now_weekday # Get entities of each model entities = [] entities_num = _reg.get('content_digest.entities_number', 10) pub_period = _datetime.now() - _timedelta(days_diff) for model in models: f = _content.find(model, language='*').gte('publish_time', pub_period).sort([ ('views_count', _odm.I_DESC) ]) entities += list(f.get(entities_num)) # Nothing to send if not entities: return # Sort all entities and cut top entities = sorted(entities, key=lambda e: e.views_count)[:entities_num] for subscriber in _odm.find('content_digest_subscriber').eq( 'enabled', True).get(): _logger.info('Preparing content digest for {}'.format( subscriber.f_get('email'))) lng = subscriber.f_get('language') default_m_subject = _lang.t('content_digest@default_mail_subject', language=lng) m_subject = _reg.get('content_digest.mail_subject_{}'.format(lng), default_m_subject) m_body = _tpl.render( _reg.get('content_digest.tpl', 'content_digest@digest'), { 'entities': entities, 'subscriber': subscriber, 'language': lng, }) _mail.Message(subscriber.f_get('email'), m_subject, m_body).send()
def get_rows(self, args: routing.ControllerArgs) -> dict: """Get browser rows. """ # Instantiate finder finder = odm.find(self._model) # Check if the user can modify/delete any entity if not odm_auth.check_model_permissions(self._model, [PERM_MODIFY, PERM_DELETE]) and \ odm_auth.check_model_permissions(self._model, [PERM_MODIFY_OWN, PERM_DELETE_OWN]): # Show only entities owned by user finder.mock.has_field('author') and finder.eq( 'author', self._current_user) # Let model to finish finder setup _api.dispense_entity(self._model).odm_ui_browser_setup_finder( finder, args) # Sort sort_order = odm.I_DESC if args.get( 'order', self.default_sort_order) in (-1, 'desc') else odm.I_ASC sort_field = args.get('sort') if sort_field and finder.mock.has_field(sort_field): finder.sort([(sort_field, sort_order)]) elif self.default_sort_field: finder.sort([(self.default_sort_field, sort_order)]) # Get root elements first finder.add_sort('_parent', pos=0) # Prepare result r = {'total': finder.count(), 'rows': []} # Build table rows cursor = finder.skip(args.get('offset', 0)).get(args.get('limit', 0)) for entity in cursor: row = entity.odm_ui_browser_row() events.fire('odm_ui@browser_row.{}'.format(self._model), entity=entity, row=row) if not row: continue # Build row's cells fields_data = { '__id': str(entity.id), '__parent': str(entity.parent.id) if entity.parent else None, } if not isinstance(row, dict): raise TypeError( '{}.odm_ui_browser_row() must return dict, got {}'.format( entity.__class__.__name__, type(row))) for df in self.data_fields: fields_data[df[0]] = row.get(df[0], ' ') # Action buttons if self._model_class.odm_ui_entity_actions_enabled() and \ (self._model_class.odm_ui_modification_allowed() or self._model_class.odm_ui_deletion_allowed()): actions = htmler.TagLessElement(child_sep=' ') for btn_data in entity.odm_ui_browser_entity_actions(self): color = 'btn btn-sm btn-' + btn_data.get( 'color', 'default btn-light') title = btn_data.get('title', '') url = btn_data.get('url') if not url: rule = btn_data.get('rule') url = router.rule_url( rule, {'ids': str(entity.id)}) if rule else '#' btn = htmler.A(href=url, css=color + ' ' + btn_data.get('css', ''), title=title, role='button') if btn_data.get('disabled'): btn.set_attr('aria_disabled', 'true') btn.add_css('disabled') btn.append_child( htmler.I(css=btn_data.get('icon', 'fa fas fa-fw fa-question'))) actions.append_child(btn) fields_data['entity-actions'] = actions.render() r['rows'].append(fields_data) return r