示例#1
0
    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
示例#2
0
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
示例#3
0
    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')
示例#4
0
    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()
示例#5
0
 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))
示例#7
0
    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)
示例#8
0
    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
示例#9
0
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
示例#10
0
    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)
示例#11
0
    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)
示例#12
0
    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)
示例#13
0
    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)
示例#14
0
    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
示例#15
0
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
示例#16
0
    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,
        }
示例#17
0
    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))
示例#18
0
    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)
示例#19
0
    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()
示例#20
0
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
示例#21
0
 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())
示例#22
0
 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())
示例#23
0
 def count_users(self, query: query.Query = None) -> int:
     return odm.find('user', query=query).count()
示例#24
0
 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()
示例#25
0
 def count_roles(self, query: query.Query = None) -> int:
     return odm.find('role', query=query).count()
示例#26
0
 def delete_thread(self, thread_uid: str):
     """Remove comments for particular thread
     """
     return odm.find('comment').eq('thread_uid', thread_uid).delete(True)
示例#27
0
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()
示例#28
0
    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], '&nbsp;')

            # 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='&nbsp;')
                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