def reset_password_by_email(self): user = self.model if not user.email: raise ValueError('User has no email') prp = self.generate_password_reset_profile() later( current_app.tasks['sendmail'].delay, user.email, render_template('email/password_reset_subject.txt'), body={ 'plain': render_template('email/password_reset.txt', activation_key=prp.activation_key, user=user), 'html': render_template('email/password_reset.html', activation_key=prp.activation_key, user=user), }, headers={ 'X-Postmaster-Msgtype': current_app.config['EMAIL_MSGTYPES']['reset_password'] }, ) return prp
def abuse_common(target_type, target): # FIXME: это всё не в bl, нехорошо (но при переносе надо не забыть про проверки доступа) user = current_user._get_current_object() if not target or not user.is_authenticated: abort(404) data = { 'page_title': 'Отправка жалобы', 'target_type': target_type, 'target': target, 'next': request.referrer or url_for('index.index'), 'abuse': None, 'can_abuse': True, 'reason': '', 'errors': [], } # Если пользователь уже отправлял жалобу, то он не может отправить её ещё раз abuse = list(AbuseReport.select( lambda x: x.target_type == target_type and x.target_id == target.id and x.user.id == user.id and not x.ignored )) abuse.sort(key=lambda x: -x.id) abuse = abuse[0] if abuse else None if abuse: data['abuse'] = abuse data['can_abuse'] = False if not abuse and request.method == 'POST': reason = request.form.get('reason') or '' data['reason'] = reason if not reason.strip(): data['errors'].append('Причина жалобы не может отсутствовать') elif len(reason) > 8192: data['errors'].append('Слишком длинно; опишите причину покороче') else: abuse = AbuseReport( target_type=target_type, target_id=target.id, user=user, reason=request.form['reason'], ) abuse.flush() # Если это первая жалоба на объект, то уведомляем админов if AbuseReport.select( lambda x: x.target_type == target_type and x.target_id == target.id and not x.ignored and x.resolved_at is None ).count() == 1: later(current_app.tasks['notify_abuse_report'].delay, abuse.id) return redirect(request.form.get('next') or url_for('index.index'), 302) if g.is_ajax: return jsonify({'page_content': {'modal': render_template('abuse_report_ajax.html', **data)}}) return render_template('abuse_report.html', **data)
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 create(self, *args, **kwargs): comment = super().create(*args, **kwargs) later(current_app.tasks['notify_story_lcomment'].delay, comment.id) assert comment.author act = comment.local.story.bl.get_activity(comment.author) if act: act.last_local_comments += 1 return comment
def set_blacklist(self, user, reason): from mini_fiction.models import StoryTag, StoryTagLog, AdminLog if not user or not user.is_staff: raise ValueError('Not authorized') tag = self.model if reason == tag.reason_to_blacklist: return old_reason = tag.reason_to_blacklist tm = datetime.utcnow() if reason: tag.reason_to_blacklist = reason tag.is_alias_for = None tag.is_hidden_alias = False story_tags = list( StoryTag.select(lambda x: x.tag == tag).prefetch( StoryTag.story)) for st in story_tags: StoryTagLog( story=st.story.id, tag=tag, tag_name=tag.name, action_flag=StoryTagLog.DELETION, modified_by=user, date=tm, ).flush() # FIXME: кажется, следующая строчка должна находиться не здесь, но я ленивый later(current_app.tasks['sphinx_update_story'].delay, st.story.id, ('tag', )) st.delete() tag.stories_count -= 1 if st.story.published: tag.published_stories_count -= 1 else: tag.reason_to_blacklist = '' tag.updated_at = tm if tag.reason_to_blacklist and old_reason: log_message = 'Изменена причина попадания тега в чёрный список.' elif tag.reason_to_blacklist: log_message = 'Тег добавлен в чёрный список.' else: log_message = 'Тег убран из чёрного списка.' AdminLog.bl.create( user=user, obj=tag, action=AdminLog.CHANGE, change_message=log_message, )
def create(self, *args, **kwargs): comment = super().create(*args, **kwargs) later(current_app.tasks['notify_story_comment'].delay, comment.id) later(current_app.tasks['sphinx_update_comments_count'].delay, comment.story.id) if comment.author: act = comment.story.bl.get_activity(comment.author) if act: act.last_comments += 1 return comment
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
def set_blacklist(self, user, reason): from mini_fiction.models import StoryTag, StoryTagLog, AdminLog if not user or not user.is_staff: raise ValueError('Not authorized') tag = self.model if reason == tag.reason_to_blacklist: return old_reason = tag.reason_to_blacklist tm = datetime.utcnow() if reason: tag.reason_to_blacklist = reason tag.is_alias_for = None tag.is_hidden_alias = False story_tags = list(StoryTag.select(lambda x: x.tag == tag).prefetch(StoryTag.story)) for st in story_tags: StoryTagLog( story=st.story.id, tag=tag, tag_name=tag.name, action_flag=StoryTagLog.DELETION, modified_by=user, date=tm, ).flush() # FIXME: кажется, следующая строчка должна находиться не здесь, но я ленивый later(current_app.tasks['sphinx_update_story'].delay, st.story.id, ('tag',)) st.delete() tag.stories_count -= 1 if st.story.published: tag.published_stories_count -= 1 else: tag.reason_to_blacklist = '' tag.updated_at = tm if tag.reason_to_blacklist and old_reason: log_message = 'Изменена причина попадания тега в чёрный список.' elif tag.reason_to_blacklist: log_message = 'Тег добавлен в чёрный список.' else: log_message = 'Тег убран из чёрного списка.' AdminLog.bl.create( user=user, obj=tag, action=AdminLog.CHANGE, change_message=log_message, )
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 reset_password_by_email(self): user = self.model if not user.email: raise ValueError('User has no email') prp = self.generate_password_reset_profile() later( current_app.tasks['sendmail'].delay, user.email, render_template('email/password_reset_subject.txt'), body={ 'plain': render_template('email/password_reset.txt', activation_key=prp.activation_key, user=user), 'html': render_template('email/password_reset.html', activation_key=prp.activation_key, user=user), }, headers={'X-Postmaster-Msgtype': current_app.config['EMAIL_MSGTYPES']['reset_password']}, ) return prp
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 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 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 create(self, *args, **kwargs): comment = super().create(*args, **kwargs) later(current_app.tasks['notify_news_comment'].delay, comment.id) return comment
def make_alias_for(self, user, canonical_tag, hidden=False): from mini_fiction.models import StoryTag, StoryTagLog, AdminLog if not user or not user.is_staff: raise ValueError('Not authorized') if canonical_tag and canonical_tag.is_alias_for: if canonical_tag.is_alias_for.is_alias_for: raise RuntimeError( 'Tag alias {} refers to another alias {}!'.format( canonical_tag.id, canonical_tag.is_alias_for.id)) canonical_tag = canonical_tag.is_alias_for tag = self.model if tag.is_alias_for == canonical_tag: if canonical_tag and bool(hidden) != tag.is_hidden_alias: tag.is_hidden_alias = bool(hidden) tag.updated_at = datetime.utcnow() AdminLog.bl.create( user=user, obj=tag, action=AdminLog.CHANGE, fields=('is_hidden_alias', ), ) return tm = datetime.utcnow() if canonical_tag: if canonical_tag == tag: raise RuntimeError('Self-reference!') tag.is_alias_for = canonical_tag tag.is_hidden_alias = bool(hidden) story_tags = list( StoryTag.select(lambda x: x.tag == tag).prefetch( StoryTag.story)) stories_with_canonical_tag = list( orm.select(x.story.id for x in StoryTag if x.tag == canonical_tag)) for st in story_tags: StoryTagLog( story=st.story.id, tag=tag, tag_name=tag.name, action_flag=StoryTagLog.DELETION, modified_by=user, date=tm, ).flush() if st.story.id not in stories_with_canonical_tag: StoryTagLog( story=st.story.id, tag=canonical_tag, tag_name=canonical_tag.name, action_flag=StoryTagLog.ADDITION, modified_by=user, date=tm, ).flush() st.tag = canonical_tag st.flush() canonical_tag.stories_count += 1 if st.story.published: canonical_tag.published_stories_count += 1 else: st.delete() later(current_app.tasks['sphinx_update_story'].delay, st.story.id, ('tag', )) tag.stories_count -= 1 if st.story.published: tag.published_stories_count -= 1 else: tag.is_alias_for = None tag.is_hidden_alias = False tag.updated_at = tm if tag.is_alias_for: log_message = 'Тег стал синонимом тега «{}».'.format( tag.is_alias_for.name) else: log_message = 'Тег перестал быть синонимом.' AdminLog.bl.create( user=user, obj=tag, action=AdminLog.CHANGE, change_message=log_message, )
def abuse_common(target_type, target): # FIXME: это всё не в bl, нехорошо (но при переносе надо не забыть про проверки доступа) user = current_user._get_current_object() if not target or not user.is_authenticated: abort(404) data = { 'page_title': 'Отправка жалобы', 'target_type': target_type, 'target': target, 'next': request.referrer or url_for('index.index'), 'abuse': None, 'can_abuse': True, 'reason': '', 'errors': [], } # Если пользователь уже отправлял жалобу, то он не может отправить её ещё раз abuse = list( AbuseReport.select( lambda x: x.target_type == target_type and x.target_id == target.id and x.user.id == user.id and not x.ignored)) abuse.sort(key=lambda x: -x.id) abuse = abuse[0] if abuse else None if abuse: data['abuse'] = abuse data['can_abuse'] = False if not abuse and request.method == 'POST': reason = request.form.get('reason') or '' data['reason'] = reason if not reason.strip(): data['errors'].append('Причина жалобы не может отсутствовать') elif len(reason) > 8192: data['errors'].append('Слишком длинно; опишите причину покороче') else: abuse = AbuseReport( target_type=target_type, target_id=target.id, user=user, reason=request.form['reason'], ) abuse.flush() # Если это первая жалоба на объект, то уведомляем админов if AbuseReport.select(lambda x: x.target_type == target_type and x. target_id == target.id and not x.ignored and x.resolved_at is None).count() == 1: later(current_app.tasks['notify_abuse_report'].delay, abuse.id) return redirect( request.form.get('next') or url_for('index.index'), 302) if g.is_ajax: return jsonify({ 'page_content': { 'modal': render_template('abuse_report_ajax.html', **data) } }) return render_template('abuse_report.html', **data)
def make_alias_for(self, user, canonical_tag, hidden=False): from mini_fiction.models import StoryTag, StoryTagLog, AdminLog if not user or not user.is_staff: raise ValueError('Not authorized') if canonical_tag and canonical_tag.is_alias_for: if canonical_tag.is_alias_for.is_alias_for: raise RuntimeError('Tag alias {} refers to another alias {}!'.format(canonical_tag.id, canonical_tag.is_alias_for.id)) canonical_tag = canonical_tag.is_alias_for tag = self.model if tag.is_alias_for == canonical_tag: if canonical_tag and bool(hidden) != tag.is_hidden_alias: tag.is_hidden_alias = bool(hidden) tag.updated_at = datetime.utcnow() AdminLog.bl.create( user=user, obj=tag, action=AdminLog.CHANGE, fields=('is_hidden_alias',), ) return tm = datetime.utcnow() if canonical_tag: if canonical_tag == tag: raise RuntimeError('Self-reference!') tag.is_alias_for = canonical_tag tag.is_hidden_alias = bool(hidden) story_tags = list(StoryTag.select(lambda x: x.tag == tag).prefetch(StoryTag.story)) stories_with_canonical_tag = list(orm.select(x.story.id for x in StoryTag if x.tag == canonical_tag)) for st in story_tags: StoryTagLog( story=st.story.id, tag=tag, tag_name=tag.name, action_flag=StoryTagLog.DELETION, modified_by=user, date=tm, ).flush() if st.story.id not in stories_with_canonical_tag: StoryTagLog( story=st.story.id, tag=canonical_tag, tag_name=canonical_tag.name, action_flag=StoryTagLog.ADDITION, modified_by=user, date=tm, ).flush() st.tag = canonical_tag st.flush() canonical_tag.stories_count += 1 if st.story.published: canonical_tag.published_stories_count += 1 else: st.delete() later(current_app.tasks['sphinx_update_story'].delay, st.story.id, ('tag',)) tag.stories_count -= 1 if st.story.published: tag.published_stories_count -= 1 else: tag.is_alias_for = None tag.is_hidden_alias = False tag.updated_at = tm if tag.is_alias_for: log_message = 'Тег стал синонимом тега «{}».'.format(tag.is_alias_for.name) else: log_message = 'Тег перестал быть синонимом.' AdminLog.bl.create( user=user, obj=tag, action=AdminLog.CHANGE, change_message=log_message, )
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