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
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
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
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
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
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
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
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
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()
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()
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
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
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
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)) ] })
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
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
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
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
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
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
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
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
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
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, )
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))
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
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
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
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
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