Ejemplo n.º 1
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(STATIC_PAGE).validated(data)

        if not author.is_superuser and data.get('is_template'):
            raise ValidationError(
                {'is_template': [lazy_gettext('Access denied')]})

        if data.get('is_template'):
            self.check_renderability(author, data['name'], data['content'])

        if not data.get('lang'):
            data['lang'] = 'none'

        exist_staticpage = self.model.get(name=data['name'], lang=data['lang'])
        if exist_staticpage:
            raise ValidationError(
                {'name': [lazy_gettext('Page already exists')]})

        staticpage = self.model(**data)
        staticpage.flush()
        AdminLog.bl.create(user=author,
                           obj=staticpage,
                           action=AdminLog.ADDITION)
        return staticpage
Ejemplo n.º 2
0
    def vote(self, author, value):
        if not author or not author.is_authenticated:
            raise ValidationError(
                {'author': [lazy_gettext("Please log in to vote")]})

        if self.can_vote:
            if not hasattr(self.model, 'votes'):
                raise NotImplementedError
            vote = self.model.votes.select(
                lambda x: x.author.id == author.id).first()
            if vote is not None:
                raise ValidationError(
                    {'author': [lazy_gettext("You already voted")]})

        if not self.can_vote or not self.can_vote_by(author):
            raise ValidationError({'author': [lazy_gettext("You can't vote")]})

        if value not in (-1, 1):
            raise ValidationError({'value': [lazy_gettext('Invalid value')]})

        vote = self.model.votes.create(author=author, vote_value=value)
        vote.flush()
        self.model.vote_total += value
        self.model.vote_count += 1
        return vote
Ejemplo n.º 3
0
    def authenticate_by_username(self, data, remote_addr=None):
        raw_data = data
        data = Validator(LOGIN).validated(data)
        user = None

        # Сначала достаём пользователя из базы
        # (без чувствительности к регистру)
        user = self.get_by_username(data.get('username'))

        # Пресекаем перебор пароля капчей (по IP и по юзеру)
        if remote_addr:
            if current_app.captcha and self.need_captcha_for_auth(
                    remote_addr, user.id if user else None):
                current_app.captcha.check_or_raise(raw_data)
            self.track_auth(remote_addr, user.id if user else None)

        # Проверяем пароль
        if not user or not user.bl.check_password(data['password']):
            if remote_addr and current_app.config['AUTH_LOG']:
                if not user:
                    current_app.logger.info(
                        '%s tried to log in, but account not found (ID: N/A, IP: %s)',
                        data.get('username', '?'), remote_addr)
                else:
                    current_app.logger.info(
                        '%s tried to log in with incorrect password (ID: %s, IP: %s)',
                        user.username, user.id, remote_addr)
            raise ValidationError({
                'username': [
                    lazy_gettext(
                        'Please enter a correct username and password.')
                ]
            })

        # Проверяем бан
        if not user.is_active:
            if remote_addr and current_app.config['AUTH_LOG']:
                current_app.logger.info(
                    '%s tried to log in, but account is disabled (ID: %s, IP: %s)',
                    user.username, user.id, remote_addr)
            raise ValidationError({
                'username':
                [user.ban_reason or lazy_gettext('Account is disabled')]
            })

        # Если дошли сюда, значит всё хорошо
        if remote_addr and current_app.config['AUTH_LOG']:
            current_app.logger.info('%s logged in (ID: %s, IP: %s)',
                                    user.username, user.id, remote_addr)
        return user
Ejemplo n.º 4
0
 def validate_and_get_picture_data(self, picture):
     fp = picture.stream
     header = fp.read(4)
     if header != b'\x89PNG':
         raise ValidationError({'picture': [lazy_gettext('PNG only')]})
     data = header + fp.read(
         16384 - 4 + 1)  # 16 KiB + 1 byte for validation
     if len(data) > 16384:
         raise ValidationError({
             'picture': [
                 lazy_gettext('Maximum picture size is {maxsize} KiB').
                 format(maxsize=16)
             ]
         })
     return data
Ejemplo n.º 5
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(TAG_CATEGORY).validated(data, update=True)
        tag_category = self.model

        if 'name' in data:
            from mini_fiction.models import TagCategory
            exist_tag_category = TagCategory.get(name=data['name'])
            if exist_tag_category and exist_tag_category.id != tag_category.id:
                raise ValidationError(
                    {'name': [lazy_gettext('Tag category already exists')]})

        changed_fields = set()
        for key, value in data.items():
            if getattr(tag_category, key) != value:
                setattr(tag_category, key, value)
                changed_fields |= {
                    key,
                }

        if changed_fields:
            AdminLog.bl.create(
                user=author,
                obj=tag_category,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return tag_category
Ejemplo n.º 6
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(CHARACTER).validated(data)

        errors = {}

        exist_character = self.model.get(name=data['name'])
        if exist_character:
            errors['name'] = [lazy_gettext('Character already exists')]

        from mini_fiction.models import CharacterGroup

        group = CharacterGroup.get(id=data['group'])
        if not group:
            errors['group'] = [lazy_gettext('Group not found')]

        if errors:
            raise ValidationError(errors)

        picture = self.validate_and_get_picture_data(data.pop('picture'))

        character = self.model(picture='pending', sha256sum='pending', **data)
        character.flush()
        character.bl.set_picture_data(picture)
        AdminLog.bl.create(user=author,
                           obj=character,
                           action=AdminLog.ADDITION)

        return character
Ejemplo n.º 7
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(CHARACTER_GROUP).validated(data, update=True)
        group = self.model

        if 'name' in data:
            from mini_fiction.models import CharacterGroup
            exist_group = CharacterGroup.get(name=data['name'])
            if exist_group and exist_group.id != group.id:
                raise ValidationError(
                    {'name': [lazy_gettext('Group already exists')]})

        changed_fields = set()
        for key, value in data.items():
            if getattr(group, key) != value:
                setattr(group, key, value)
                changed_fields |= {
                    key,
                }

        if changed_fields:
            AdminLog.bl.create(
                user=author,
                obj=group,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return group
Ejemplo n.º 8
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(CLASSIFIER).validated(data, update=True)
        classifier = self.model

        if 'name' in data:
            from mini_fiction.models import Classifier
            exist_classifier = Classifier.get(name=data['name'])
            if exist_classifier and exist_classifier.id != classifier.id:
                raise ValidationError(
                    {'name': [lazy_gettext('Classifier already exists')]})

        changed_fields = set()
        for key, value in data.items():
            if getattr(classifier, key) != value:
                setattr(classifier, key, value)
                changed_fields |= {
                    key,
                }

        if changed_fields:
            AdminLog.bl.create(
                user=author,
                obj=classifier,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return classifier
Ejemplo n.º 9
0
    def delete(self, author):
        from mini_fiction.models import AdminLog

        htmlblock = self.model
        if htmlblock.is_template and not author.is_superuser:
            raise ValidationError({'is_template': [lazy_gettext('Access denied')]})
        later(self.clear_cache, htmlblock.name)
        AdminLog.bl.create(user=author, obj=htmlblock, action=AdminLog.DELETION)
        htmlblock.delete()
Ejemplo n.º 10
0
    def delete(self, author):
        from mini_fiction.models import AdminLog

        staticpage = self.model
        if staticpage.name in ('help', 'terms',
                               'robots.txt') and staticpage.lang == 'none':
            raise ValidationError({
                'is_template':
                [lazy_gettext('This is system page, you cannot delete it')]
            })
        if staticpage.is_template and not author.is_superuser:
            raise ValidationError(
                {'is_template': [lazy_gettext('Access denied')]})

        AdminLog.bl.create(user=author,
                           obj=staticpage,
                           action=AdminLog.DELETION)
        staticpage.delete()
Ejemplo n.º 11
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(NEWS_ITEM).validated(data, update=True)
        newsitem = self.model

        if not author.is_superuser and (newsitem.is_template
                                        or data.get('is_template')):
            raise ValidationError(
                {'is_template': [lazy_gettext('Access denied')]})

        if 'name' in data:
            from mini_fiction.models import NewsItem
            exist_newsitem = NewsItem.get(name=data['name'])
            if exist_newsitem and exist_newsitem.id != newsitem.id:
                raise ValidationError(
                    {'name': [lazy_gettext('Page already exists')]})

        if data.get('is_template', newsitem.is_template) and 'content' in data:
            self.check_renderability(author, data.get('name', newsitem.name),
                                     data['content'])

        if data.get('show') and not newsitem.show:
            self.hide_shown_newsitem()

        changed_fields = set()
        for key, value in data.items():
            if getattr(newsitem, key) != value:
                setattr(newsitem, key, value)
                changed_fields |= {
                    key,
                }

        if changed_fields:
            AdminLog.bl.create(
                user=author,
                obj=newsitem,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return newsitem
Ejemplo n.º 12
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(STATIC_PAGE).validated(data, update=True)
        staticpage = self.model

        if not author.is_superuser and (staticpage.is_template
                                        or data.get('is_template')):
            raise ValidationError(
                {'is_template': [lazy_gettext('Access denied')]})

        if ('name' in data and data['name'] != staticpage.name) or (
                'lang' in data and data['lang'] != staticpage.lang):
            raise ValidationError({
                'name': [lazy_gettext('Cannot change primary key')],
                'lang': [lazy_gettext('Cannot change primary key')],
            })

        if data.get('is_template',
                    staticpage.is_template) and 'content' in data:
            self.check_renderability(author, data.get('name', staticpage.name),
                                     data['content'])

        changed_fields = set()
        for key, value in data.items():
            if getattr(staticpage, key) != value:
                setattr(staticpage, key, value)
                changed_fields |= {
                    key,
                }

        if changed_fields:
            AdminLog.bl.create(
                user=author,
                obj=staticpage,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return staticpage
Ejemplo n.º 13
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(HTML_BLOCK).validated(data, update=True)
        htmlblock = self.model

        if not author.is_superuser and (htmlblock.is_template or data.get('is_template')):
            raise ValidationError({'is_template': [lazy_gettext('Access denied')]})

        if ('name' in data and data['name'] != htmlblock.name) or ('lang' in data and data['lang'] != htmlblock.lang):
            raise ValidationError({
                'name': [lazy_gettext('Cannot change primary key')],
                'lang': [lazy_gettext('Cannot change primary key')],
            })

        if data.get('is_template', htmlblock.is_template) and 'content' in data:
            self.check_renderability(author, data.get('name', htmlblock.name), data['content'])

        changed_fields = set()
        old_name = htmlblock.name

        for key, value in data.items():
            if getattr(htmlblock, key) != value:
                setattr(htmlblock, key, value)
                changed_fields |= {key,}

        later(self.clear_cache, old_name)
        if htmlblock.name != old_name:
            later(self.clear_cache, htmlblock.name)

        if changed_fields:
            AdminLog.bl.create(
                user=author,
                obj=htmlblock,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return htmlblock
Ejemplo n.º 14
0
    def check_renderability(self, author, name, content):
        try:
            template = current_app.jinja_env.from_string(content)
            template.name = 'db/staticpages/{}.html'.format(name)
        except Exception as exc:
            raise ValidationError({
                'content': [
                    lazy_gettext('Cannot parse staticpage "{0}": {1}').format(
                        name, str(exc))
                ]
            })

        from mini_fiction.models import AnonymousUser

        try:
            render_template(template,
                            staticpage_name=name,
                            current_user=AnonymousUser())
        except Exception as exc:
            raise ValidationError({
                'content': [
                    lazy_gettext(
                        'Cannot render staticpage "{0}" for anonymous: {1}').
                    format(name, str(exc))
                ]
            })

        try:
            render_template(template,
                            staticpage_name=name,
                            current_user=author)
        except Exception as exc:
            raise ValidationError({
                'content': [
                    lazy_gettext(
                        'Cannot render staticpage "{0}" for you: {1}').format(
                            name, str(exc))
                ]
            })
Ejemplo n.º 15
0
    def create(self, data):
        if 'username' not in data:
            raise ValidationError(
                {'username': [lazy_gettext('Please set username')]})

        if data.get('password') and data.get('password_hash'):
            raise ValidationError({
                'password':
                [lazy_gettext('Please set only password or password hash')]
            })

        errors = {}
        if data.get('password') and current_app.config[
                'CHECK_PASSWORDS_SECURITY'] and not self.is_password_good(
                    data['password'], extra=(data['username'], )):
            errors['password'] = [
                lazy_gettext('Password is too bad, please change it')
            ]
        errors.update(self._check_existence(data['username'], data['email']))
        if errors:
            raise ValidationError(errors)

        user = self._model()(
            username=data['username'],
            email=data.get('email') or '',
            is_active=bool(data.get('is_active', True)),
            ban_reason=data.get('ban_reason') or '',
            is_staff=bool(data.get('is_staff', False)),
            is_superuser=bool(data.get('is_superuser', False)),
            date_joined=data.get('date_joined', datetime.utcnow()),
            activated_at=data.get('activated_at', None),
            session_token=utils_random.random_string(32),
            password=data.get('password_hash') or '',
        )
        user.flush()  # for user.id
        if data.get('password'):
            user.bl.set_password(data['password'])
        return user
Ejemplo n.º 16
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(CHARACTER_GROUP).validated(data)

        exist_group = self.model.get(name=data['name'])
        if exist_group:
            raise ValidationError(
                {'name': [lazy_gettext('Group already exists')]})

        group = self.model(**data)
        group.flush()
        AdminLog.bl.create(user=author, obj=group, action=AdminLog.ADDITION)
        return group
Ejemplo n.º 17
0
    def create(self, user, data):
        from mini_fiction.models import Tag, AdminLog

        if not user or not user.is_staff:
            raise ValueError('Not authorized')

        data = Validator(TAG).validated(data)

        errors = {}

        bad_reason = self.validate_tag_name(data['name'])
        if bad_reason:
            errors['name'] = [bad_reason]

        iname = normalize_tag(data['name'])
        if not bad_reason and Tag.get(iname=iname):
            errors['name'] = [lazy_gettext('Tag already exists')]

        canonical_tag = None
        if data.get('is_alias_for'):
            canonical_tag = Tag.get(iname=normalize_tag(data['is_alias_for']))
            if not canonical_tag:
                errors['is_alias_for'] = [lazy_gettext('Tag not found')]

        if errors:
            raise ValidationError(errors)

        tag = Tag(
            name=data['name'],
            iname=iname,
            category=data.get('category'),
            color=data.get('color') or '',
            description=data.get('description') or '',
            is_main_tag=data.get('is_main_tag', False),
            created_by=user,
            is_alias_for=None,
            reason_to_blacklist='',
        )
        tag.flush()

        AdminLog.bl.create(user=user, obj=tag, action=AdminLog.ADDITION)

        if data.get('reason_to_blacklist'):
            tag.bl.set_blacklist(user, data['reason_to_blacklist'])
        elif canonical_tag:
            tag.bl.make_alias_for(user,
                                  canonical_tag,
                                  hidden=data.get('is_hidden_alias', False))

        return tag
Ejemplo n.º 18
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(HTML_BLOCK).validated(data)

        if not author.is_superuser and data.get('is_template'):
            raise ValidationError({'is_template': [lazy_gettext('Access denied')]})

        if data.get('is_template'):
            self.check_renderability(author, data['name'], data['content'])

        if not data.get('lang'):
            data['lang'] = 'none'

        exist_htmlblock = self.model.get(name=data['name'], lang=data['lang'])
        if exist_htmlblock:
            raise ValidationError({'name': [lazy_gettext('Block already exists')]})

        htmlblock = self.model(**data)
        htmlblock.flush()
        AdminLog.bl.create(user=author, obj=htmlblock, action=AdminLog.ADDITION)
        later(self.clear_cache, htmlblock.name)  # avoid race condition by `later` (runs after commit)
        return htmlblock
Ejemplo n.º 19
0
    def create(self, user, obj, action, fields=None, change_message=None):
        from mini_fiction.models import AdminLog

        if action not in (AdminLog.ADDITION, AdminLog.CHANGE,
                          AdminLog.DELETION):
            raise ValidationError({'action': ['Invalid action']})

        if change_message is None:
            if fields is None and action == AdminLog.CHANGE:
                raise ValidationError(
                    {'fields': ['Please set fields or change_message']})
            change_message = ''
            if fields:
                # Срисовал из джанги 1.5
                msg = ['Изменен ']
                for i, f in enumerate(fields):
                    if i > 0:
                        msg.append(' и ' if i == len(fields) - 1 else ', ')
                    msg.append(f)
                msg.append('.')
                change_message = ''.join(msg)
                del msg

        type_str = str(obj.__class__.__name__).lower()
        type_id = self.get_or_create_type_id(type_str)

        logitem = AdminLog(
            user=user,
            type=type_id,
            object_id=str(obj.get_pk()),
            object_repr=str(obj),
            action_flag=action,
            change_message=change_message,
        )
        logitem.flush()
        return logitem
Ejemplo n.º 20
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(NEWS_ITEM).validated(data)

        if not author.is_superuser and data.get('is_template'):
            raise ValidationError(
                {'is_template': [lazy_gettext('Access denied')]})

        if data.get('is_template'):
            self.check_renderability(author, data['name'], data['content'])

        exist_newsitem = self.model.get(name=data['name'])
        if exist_newsitem:
            raise ValidationError(
                {'name': [lazy_gettext('Page already exists')]})

        if data.get('show'):
            self.hide_shown_newsitem()

        newsitem = self.model(author=author, **data)
        newsitem.flush()
        AdminLog.bl.create(user=author, obj=newsitem, action=AdminLog.ADDITION)
        return newsitem
Ejemplo n.º 21
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(CATEGORY).validated(data)

        exist_category = self.model.get(name=data['name'])
        if exist_category:
            raise ValidationError(
                {'name': [lazy_gettext('Category already exists')]})

        category = self.model(**data)
        category.flush()
        AdminLog.bl.create(user=author, obj=category, action=AdminLog.ADDITION)

        return category
Ejemplo n.º 22
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(CLASSIFIER).validated(data)

        exist_classifier = self.model.get(name=data['name'])
        if exist_classifier:
            raise ValidationError(
                {'name': [lazy_gettext('Classifier already exists')]})

        classifier = self.model(**data)
        classifier.flush()
        AdminLog.bl.create(user=author,
                           obj=classifier,
                           action=AdminLog.ADDITION)
        return classifier
Ejemplo n.º 23
0
    def register(self, data):
        from mini_fiction.models import RegistrationProfile

        if current_app.captcha:
            current_app.captcha.check_or_raise(data)

        data = Validator(REGISTRATION).validated(data)
        errors = self._check_existence(data['username'], data['email'])
        if errors:
            raise ValidationError(errors)

        old_rp = RegistrationProfile.get(username=data['username'],
                                         email=data['email'])
        if old_rp:
            old_rp.delete()
        del old_rp

        rp = RegistrationProfile(
            activation_key=utils_random.random_string(40),
            email=data['email'],
            password=self.generate_password_hash(data['password']),
            username=data['username'],
        )
        rp.flush()

        later(
            current_app.tasks['sendmail'].delay,
            data['email'],
            render_template('email/activation_subject.txt'),
            body={
                'plain':
                render_template('email/activation.txt',
                                activation_key=rp.activation_key),
                'html':
                render_template('email/activation.html',
                                activation_key=rp.activation_key),
            },
            headers={
                'X-Postmaster-Msgtype':
                current_app.config['EMAIL_MSGTYPES']['registration']
            },
        )

        return rp
Ejemplo n.º 24
0
def login():
    page_title = gettext('Authorization')

    captcha = None
    captcha_error = None

    form = LoginForm()
    if form.validate_on_submit():
        data = dict(form.data)
        if current_app.captcha:
            data = current_app.captcha.copy_fields(data, request.form)

        try:
            user = Author.bl.authenticate_by_username(data,
                                                      request.remote_addr)
            if not login_user(user, remember=True):
                raise ValidationError({'username': [gettext('Cannot login')]})
        except ValidationError as exc:
            form.set_errors(exc.errors)
        except CaptchaError:
            captcha_error = 'Вы не доказали, что вы не робот'
            if not captcha:
                captcha = current_app.captcha.generate()
        else:
            user.last_login = datetime.utcnow()
            user.last_visit = user.last_login
            next_url = request.args.get('next')
            if not next_url or len(next_url) < 2 or next_url[
                    0] != '/' or next_url.startswith('//'):
                next_url = url_for('index.index')
            return redirect(next_url)

    if not captcha and current_app.captcha and Author.bl.need_captcha_for_auth(
            request.remote_addr):
        captcha = current_app.captcha.generate()

    return render_template(
        'registration/login.html',
        form=form,
        page_title=page_title,
        captcha=captcha,
        captcha_error=captcha_error,
    )
Ejemplo n.º 25
0
    def update(self, user, data):
        from mini_fiction.models import Tag, AdminLog

        if not user or not user.is_staff:
            raise ValueError('Not authorized')

        tag = self.model

        data = Validator(TAG).validated(data, update=True)
        changes = {}
        errors = {}

        if 'name' in data and data['name'] != tag.name:
            bad_reason = self.validate_tag_name(data['name'])
            if bad_reason:
                errors['name'] = [bad_reason]

            iname = normalize_tag(data['name'])
            if not bad_reason and iname != tag.iname and Tag.get(iname=iname):
                errors['name'] = [lazy_gettext('Tag already exists')]

            changes['name'] = data['name']
            if iname != tag.iname:
                changes['iname'] = iname

        if 'category' in data:
            old_category_id = tag.category.id if tag.category else None
            if old_category_id != data['category']:
                changes['category'] = data['category']

        for key in ('color', 'description', 'is_main_tag'):
            if key in data and data[key] != getattr(tag, key):
                changes[key] = data[key]

        canonical_tag = tag.is_alias_for
        if 'is_alias_for' in data:
            if data.get('is_alias_for'):
                canonical_tag = Tag.get(
                    iname=normalize_tag(data['is_alias_for']))
                if not canonical_tag:
                    errors['is_alias_for'] = [lazy_gettext('Tag not found')]
                elif canonical_tag == tag or canonical_tag.is_alias_for and canonical_tag.is_alias_for == tag:
                    errors['is_alias_for'] = [
                        lazy_gettext('Tag cannot refer to itself')
                    ]
            else:
                canonical_tag = None

        if errors:
            raise ValidationError(errors)
        if changes:
            changes['updated_at'] = datetime.utcnow()
            tag.set(**changes)

            AdminLog.bl.create(
                user=user,
                obj=tag,
                action=AdminLog.CHANGE,
                fields=set(changes) - {'updated_at'},
            )

        if 'reason_to_blacklist' in data:
            self.set_blacklist(user, data['reason_to_blacklist'])
        if not tag.is_blacklisted and ('is_alias_for' in data
                                       or 'is_hidden_alias' in data):
            self.make_alias_for(
                user, canonical_tag,
                data.get('is_hidden_alias', tag.is_hidden_alias))
Ejemplo n.º 26
0
    def create(self, target, author, ip, data):
        if self.schema is None:
            raise NotImplementedError
        reqs = self.access_for_commenting_by(target, author)
        if not reqs:  # нет доступа
            raise ValueError('Permission denied')  # TODO: refactor exceptions

        if reqs.get('captcha'):
            if current_app.captcha:
                current_app.captcha.check_or_raise(data)

        if self.schema:
            data = Validator(self.schema).validated(data)

        if data.get('parent'):
            parent = target.comments.select(lambda x: x.local_id == data[
                'parent'] and not x.deleted).first()
            if not parent:
                raise ValidationError(
                    {'parent': [lazy_gettext('Parent comment not found')]})
            if not parent.bl.access_for_answer_by(author):
                raise ValueError('Permission denied')
        else:
            parent = None

        tm = datetime.utcnow()

        data = {
            self.target_attr:
            target,
            'author':
            author if author and author.is_authenticated else None,
            'author_username':
            author.username if author and author.is_authenticated else '',
            'ip':
            ipaddress.ip_address(ip).exploded,
            'parent':
            parent,
            'tree_depth':
            parent.tree_depth + 1 if parent else 0,
            'text':
            data['text'],
            'date':
            tm,
            'edits_count':
            0,
            'last_edited_at':
            tm,
            'last_edited_by':
            author if author and author.is_authenticated else None,
        }
        if parent:
            assert parent.root_id
            data['root_id'] = parent.root_id
        else:
            data['root_id'] = 0  # заполним после flush

        last_comment = target.comments.select().order_by(
            self.model.id.desc()).first()
        data['local_id'] = (last_comment.local_id + 1) if last_comment else 1

        data.update(self._attributes_for(data))

        comment = self.model(**data)
        comment.flush()
        assert comment.id
        if not parent:
            comment.root_id = comment.id
        if hasattr(target, 'comments_count'):
            target.comments_count += 1
        if hasattr(target, 'last_comment_id'):
            target.last_comment_id = comment.id
        if parent:
            parent.answers_count += 1

        current_app.cache.delete('index_comments_html')

        return comment
Ejemplo n.º 27
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(CHARACTER_FOR_UPDATE).validated(data, update=True)
        character = self.model

        errors = {}

        if 'name' in data:
            from mini_fiction.models import Character
            exist_character = Character.get(name=data['name'])
            if exist_character and exist_character.id != character.id:
                errors['name'] = [lazy_gettext('Character already exists')]

        if 'group' in data:
            from mini_fiction.models import CharacterGroup

            group = CharacterGroup.get(id=data['group'])
            if not group:
                errors['group'] = [lazy_gettext('Group not found')]
        else:
            group = None

        if errors:
            raise ValidationError(errors)

        changed_fields = set()

        if data.get('picture'):
            picture = self.validate_and_get_picture_data(data['picture'])
        else:
            picture = None

        for key, value in data.items():
            if key == 'picture':
                if picture is not None:
                    self.set_picture_data(picture)
                    changed_fields |= {
                        'picture',
                    }
            elif key == 'group':
                if character.group.id != value:
                    setattr(character, key, value)
                    changed_fields |= {
                        key,
                    }
            elif getattr(character, key) != value:
                setattr(character, key, value)
                changed_fields |= {
                    key,
                }

        if changed_fields:
            AdminLog.bl.create(
                user=author,
                obj=character,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return character
Ejemplo n.º 28
0
    def update(self, data, modified_by_user=None, fill_admin_log=False):
        from mini_fiction.models import ChangeEmailProfile

        user = self.model
        changed_fields = set()

        if 'email' in data and data['email'] != user.email:
            user.email = data['email']
            cep = ChangeEmailProfile.get(user=user)
            if cep:
                cep.delete()
            changed_fields |= {
                'email',
            }

        for field in ('bio', 'premoderation_mode', 'ban_reason'):
            if field in data and getattr(user, field) != data[field]:
                setattr(user, field, data[field])
                changed_fields |= {
                    field,
                }

        for field in ('is_staff', 'is_active', 'is_superuser', 'detail_view',
                      'nsfw'):
            if field in data and getattr(user, field) != bool(data[field]):
                setattr(user, field, bool(data[field]))
                changed_fields |= {
                    field,
                }

        if 'excluded_categories' in data:
            user.excluded_categories = ','.join(
                str(x) for x in data['excluded_categories'])
            changed_fields |= {
                'excluded_categories',
            }

        if 'comments_per_page' in data:
            if user.comments_per_page is None and data[
                    'comments_per_page'] == current_app.config[
                        'COMMENTS_COUNT']['page']:
                pass  # Если бралось значение из настроек проекта, то его и оставляем
            else:
                value = min(1000, max(1, int(data['comments_per_page'])))
                if user.comments_per_page != value:
                    user.comments_per_page = value
                    changed_fields |= {
                        'comments_per_page',
                    }

        if 'comments_maxdepth' in data:
            if user.comments_maxdepth is None and data[
                    'comments_maxdepth'] == current_app.config[
                        'COMMENTS_TREE_MAXDEPTH']:
                pass  # Если бралось значение из настроек проекта, то его и оставляем
            elif user.comments_maxdepth != int(data['comments_maxdepth']):
                user.comments_maxdepth = int(data['comments_maxdepth'])
                changed_fields |= {
                    'comments_maxdepth',
                }

        if 'comment_spoiler_threshold' in data:
            if user.comment_spoiler_threshold is None and data[
                    'comment_spoiler_threshold'] == current_app.config[
                        'COMMENT_SPOILER_THRESHOLD']:
                pass  # Если бралось значение из настроек проекта, то его и оставляем
            elif user.comment_spoiler_threshold != int(
                    data['comment_spoiler_threshold']):
                user.comment_spoiler_threshold = int(
                    data['comment_spoiler_threshold'])
                changed_fields |= {
                    'comment_spoiler_threshold',
                }

        if data.get('header_mode') in ('off', 'l', 'ls'):
            # off - не показывать картинку в шапке
            # l - показывать только картинку
            # ls - показывать картинку с блоком случайных рассказов
            # Если в базе пусто, то значение по умолчанию ls
            if (user.header_mode or 'ls') != data['header_mode']:
                user.header_mode = data['header_mode']
                changed_fields |= {
                    'header_mode',
                }

        if 'contacts' in data:
            contacts = [
                x for x in data['contacts'] if x.get('name') and x.get('value')
            ]
            lenc = len(contacts)

            schemas = {}
            for x in current_app.config['CONTACTS']:
                schema = dict(x.get('schema') or {})
                schema['type'] = 'string'
                schema['maxlength'] = 255
                schemas[x['name']] = schema

            errors = {}
            for i, x in enumerate(contacts):
                if x.get('name') not in schemas:
                    errors[i] = {
                        'name': [lazy_gettext('Invalid contact type')]
                    }
                    continue
                schema = dict(schemas[x['name']])
                v = Validator({'value': schema})
                v.validate({'value': x['value']})
                if v.errors:
                    errors[i] = v.errors

            if errors:
                raise ValidationError({'contacts': errors})

            from mini_fiction.models import Contact

            old_contacts = list(
                Contact.select(lambda x: x.author == user).order_by(
                    Contact.id))
            while len(old_contacts) > lenc:
                old_contacts.pop().delete()

            for oldc, newc in zip(old_contacts, contacts):
                if oldc.name != newc['name']:
                    oldc.name = newc['name']
                if oldc.value != newc['value']:
                    oldc.value = newc['value']

            i = len(old_contacts)
            while i < lenc:
                old_contacts.append(
                    Contact(
                        author=user,
                        name=contacts[i]['name'],
                        value=contacts[i]['value'],
                    ))
                i = len(old_contacts)

            changed_fields |= {
                'contacts',
            }  # TODO: надо ли?

        if data.get('delete_avatar'):
            self.delete_avatar()
            changed_fields |= {'avatar_small', 'avatar_medium', 'avatar_large'}

        elif current_app.config['AVATARS_UPLOADING'] and data.get('avatar'):
            from PIL import Image

            image_data = data['avatar'].stream.read(256 * 1024 + 1)
            if len(image_data) > 256 * 1024:
                raise ValidationError({
                    'avatar': [
                        lazy_gettext(
                            'Too big avatar; must be {value} KiB or smaller').
                        format(value=256)
                    ]
                })

            try:
                image = Image.open(BytesIO(image_data))
            except Exception:
                raise ValidationError(
                    {'avatar': [lazy_gettext('Cannot read avatar')]})
            else:
                with image:
                    self.validate_and_set_avatar(image, image_data)
                changed_fields |= {
                    'avatar_small', 'avatar_medium', 'avatar_large'
                }

        if data.get('extra') is not None:
            if isinstance(data['extra'], str):
                try:
                    json.loads(data['extra'])
                except Exception:
                    raise ValidationError({'extra': ['Invalid extra JSON']})
                user.extra = data['extra']
                changed_fields |= {
                    'extra',
                }
            elif not isinstance(data['extra'], dict):
                raise ValidationError(
                    {'extra': ['extra must be dict or JSON string']})
            else:
                user.extra = json.dumps(data['extra'], ensure_ascii=False)
                changed_fields |= {
                    'extra',
                }

        if modified_by_user and fill_admin_log and changed_fields:
            from mini_fiction.models import AdminLog
            AdminLog.bl.create(
                user=modified_by_user,
                obj=user,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return changed_fields
Ejemplo n.º 29
0
    def validate_and_set_avatar(self, image, image_data=None):
        if not current_app.config['AVATARS_UPLOADING']:
            raise ValidationError({'avatar': ['Avatar uploading is disabled']})

        from PIL import Image

        user = self.model

        # Валидация размера
        errors = []
        if image.size[0] < 16 or image.size[1] < 16:
            errors.append(
                lazy_gettext(
                    'Too small avatar; must be {w}x{h} or bigger').format(
                        w=16, h=16))
        if image.size[0] > 512 or image.size[1] > 512:
            errors.append(
                lazy_gettext(
                    'Too big avatar; must be {w}x{h} or smaller').format(
                        w=512, h=512))

        if errors:
            raise ValidationError({'avatar': errors})

        # Выбор формата для сохранения
        if image.format == 'JPEG':
            frmt = 'JPEG'
            ext = 'jpg'
        elif image.format == 'GIF':
            frmt = 'GIF'
            ext = 'gif'
        else:
            frmt = 'PNG'
            ext = 'png'

        result = {}

        # Пути для сохранения
        urlpath = '/'.join(
            ('avatars', str(user.id)))  # equivalent to ospath except Windows!
        ospath = os.path.join(current_app.config['MEDIA_ROOT'], 'avatars',
                              str(user.id))
        prefix = str(int(time.time()) - 1451606400) + '_'
        if not os.path.isdir(ospath):
            os.makedirs(ospath)

        # Обрезка под квадрат
        if image.size[0] > image.size[1]:
            offset = (image.size[0] - image.size[1]) // 2
            cropped = image.crop(
                (offset, 0, image.size[1] + offset, image.size[1]))
        elif image.size[0] < image.size[1]:
            offset = (image.size[1] - image.size[0]) // 2
            cropped = image.crop(
                (0, offset, image.size[0], image.size[0] + offset))
        else:
            cropped = image.copy()

        cropped.load()
        with cropped:
            # Сохраняем три размера
            mindim = min(cropped.size)
            for name, dim in (('small', 24), ('medium', 100), ('large', 256)):
                size = (min(mindim, dim), min(mindim, dim))
                filename = prefix + name + '.' + ext
                result[name] = urlpath + '/' + filename
                filepath = os.path.join(ospath, filename)

                if cropped.size == size:
                    # Если можем, сохраняем картинку как есть
                    if image.size == size and image_data and image.format == frmt:
                        with open(filepath, 'wb') as fp:
                            fp.write(image_data)
                    else:
                        # При отличающемся формате или отсутствии оригинала пересохраняем
                        cropped.save(filepath, frmt, quality=92)
                else:
                    # При неподходящем размере изменяем и сохраняем
                    with cropped.resize(size, Image.ANTIALIAS) as resized:
                        resized.save(filepath, frmt, quality=92)

        # Удаляем старую аватарку с ФС
        self.delete_avatar()

        # Сохраняем в БД
        user.avatar_small = result['small']
        user.avatar_medium = result['medium']
        user.avatar_large = result['large']

        # Возвращаем имена сохранённых файлов
        return result
Ejemplo n.º 30
0
    def update_email_with_confirmation(self, email):
        from mini_fiction.models import Author, ChangeEmailProfile

        user = self.model

        cep = ChangeEmailProfile.get(user=user)
        cep_expired = not cep or cep.date + timedelta(
            days=current_app.config['ACCOUNT_ACTIVATION_DAYS']
        ) < datetime.utcnow()

        if email.lower() == user.email.lower():
            if cep:
                # Отмена установки новой почты
                cep.delete()
            return False

        if Author.bl.is_email_busy(email):
            raise ValidationError(
                {'email': [lazy_gettext('Email address is already in use')]})

        data = {
            'date': datetime.utcnow(),
            'activation_key': utils_random.random_string(40),
            'user': user,
            'email': email,
        }

        if not cep:
            cep = ChangeEmailProfile(**data)
        elif cep_expired or email.lower() != cep.email.lower():
            for k, v in data.items():
                setattr(cep, k, v)

        later(
            current_app.tasks['sendmail'].delay,
            cep.email,
            render_template('email/change_email_subject.txt'),
            body={
                'plain':
                render_template('email/change_email.txt',
                                activation_key=cep.activation_key,
                                user=user),
                'html':
                render_template('email/change_email.html',
                                activation_key=cep.activation_key,
                                user=user),
            },
            headers={
                'X-Postmaster-Msgtype':
                current_app.config['EMAIL_MSGTYPES']['change_email']
            },
        )

        if user.email:
            later(
                current_app.tasks['sendmail'].delay,
                user.email,
                render_template('email/change_email_warning_subject.txt'),
                body={
                    'plain':
                    render_template('email/change_email_warning.txt',
                                    user=user,
                                    new_email=email),
                    'html':
                    render_template('email/change_email_warning.html',
                                    user=user,
                                    new_email=email),
                },
                headers={
                    'X-Postmaster-Msgtype':
                    current_app.config['EMAIL_MSGTYPES']
                    ['change_email_warning']
                },
            )

        return True