コード例 #1
0
ファイル: sorting.py プロジェクト: ra2003/mini_fiction
    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
コード例 #2
0
ファイル: tag_categories.py プロジェクト: ra2003/mini_fiction
    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
コード例 #3
0
ファイル: sorting.py プロジェクト: ra2003/mini_fiction
    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
コード例 #4
0
ファイル: sorting.py プロジェクト: andreymal/mini_fiction
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(CATEGORY).validated(data, update=True)
        category = self.model

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

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

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

        return category
コード例 #5
0
ファイル: logopics.py プロジェクト: andreymal/mini_fiction
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(LOGOPIC_FOR_UPDATE).validated(data, update=True)
        logopic = self.model

        changed_fields = set()

        for key, value in data.items():
            if key == 'picture':
                if value:
                    self.set_picture_data(value)
                    changed_fields |= {'picture',}
            else:
                if key == 'original_link_label':
                    value = value.replace('\r', '')
                if getattr(logopic, key) != value:
                    setattr(logopic, key, value)
                    changed_fields |= {key,}

        if changed_fields:
            logopic.updated_at = datetime.utcnow()
            current_app.cache.delete('logopics')

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

        return logopic
コード例 #6
0
ファイル: sorting.py プロジェクト: andreymal/mini_fiction
    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
コード例 #7
0
ファイル: sorting.py プロジェクト: andreymal/mini_fiction
    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
コード例 #8
0
ファイル: authors.py プロジェクト: andreymal/mini_fiction
    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
コード例 #9
0
ファイル: staticpages.py プロジェクト: andreymal/mini_fiction
    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
コード例 #10
0
ファイル: sorting.py プロジェクト: ra2003/mini_fiction
    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
コード例 #11
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
コード例 #12
0
ファイル: sorting.py プロジェクト: andreymal/mini_fiction
    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
コード例 #13
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(LOGOPIC).validated(data)

        picture = data.pop('picture')
        logopic = self.model(picture='pending', sha256sum='pending', **data)
        logopic.flush()
        logopic.bl.set_picture_data(picture)
        current_app.cache.delete('logopics')
        AdminLog.bl.create(user=author, obj=logopic, action=AdminLog.ADDITION)
        return logopic
コード例 #14
0
ファイル: logopics.py プロジェクト: andreymal/mini_fiction
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(LOGOPIC).validated(data)

        picture = data.pop('picture')
        logopic = self.model(picture='pending', sha256sum='pending', **data)
        logopic.flush()
        logopic.bl.set_picture_data(picture)
        current_app.cache.delete('logopics')
        AdminLog.bl.create(user=author, obj=logopic, action=AdminLog.ADDITION)
        return logopic
コード例 #15
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
コード例 #16
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
コード例 #17
0
ファイル: news.py プロジェクト: andreymal/mini_fiction
    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
コード例 #18
0
ファイル: staticpages.py プロジェクト: andreymal/mini_fiction
    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
コード例 #19
0
ファイル: tags.py プロジェクト: andreymal/mini_fiction
    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
コード例 #20
0
ファイル: htmlblocks.py プロジェクト: ra2003/mini_fiction
    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
コード例 #21
0
ファイル: sorting.py プロジェクト: ra2003/mini_fiction
    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
コード例 #22
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(LOGOPIC_FOR_UPDATE).validated(data, update=True)
        logopic = self.model

        changed_fields = set()

        for key, value in data.items():
            if key == 'picture':
                if value:
                    self.set_picture_data(value)
                    changed_fields |= {
                        'picture',
                    }
            else:
                if key == 'original_link_label':
                    value = value.replace('\r', '')
                if getattr(logopic, key) != value:
                    setattr(logopic, key, value)
                    changed_fields |= {
                        key,
                    }

        if changed_fields:
            logopic.updated_at = datetime.utcnow()
            current_app.cache.delete('logopics')

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

        return logopic
コード例 #23
0
ファイル: news.py プロジェクト: ra2003/mini_fiction
    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
コード例 #24
0
ファイル: sorting.py プロジェクト: ra2003/mini_fiction
    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
コード例 #25
0
ファイル: sorting.py プロジェクト: ra2003/mini_fiction
    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
コード例 #26
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
コード例 #27
0
ファイル: news.py プロジェクト: ra2003/mini_fiction
    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
コード例 #28
0
    def update(self, author, ip, data):
        if not self.can_update:
            raise ValueError('Not available')

        if self.schema is None or not hasattr(self.model, 'edits'):
            raise NotImplementedError
        if not self.can_update_by(author):
            raise ValueError('Permission denied')  # TODO: refactor exceptions

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

        comment = self.model
        old_text = comment.text
        new_text = data['text']

        if old_text == new_text:
            return None

        comment.text = new_text
        comment.edits_count += 1
        comment.last_edited_at = datetime.utcnow()
        comment.last_edited_by = author
        comment.flush()

        editlog = comment.edits.create(
            editor=author,
            date=comment.last_edited_at,
            old_text=old_text,
            new_text=new_text,
            ip=ipaddress.ip_address(ip).exploded,
        )
        editlog.flush()

        current_app.cache.delete('index_comments_html')

        return editlog
コード例 #29
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
コード例 #30
0
ファイル: htmlblocks.py プロジェクト: ra2003/mini_fiction
    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
コード例 #31
0
ファイル: news.py プロジェクト: andreymal/mini_fiction
    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
コード例 #32
0
ファイル: tags.py プロジェクト: andreymal/mini_fiction
    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))
コード例 #33
0
ファイル: sorting.py プロジェクト: ra2003/mini_fiction
    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
コード例 #34
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
コード例 #35
0
ファイル: comments.py プロジェクト: andreymal/mini_fiction
    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
コード例 #36
0
ファイル: authors.py プロジェクト: andreymal/mini_fiction
    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
コード例 #37
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))
コード例 #38
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