Exemplo n.º 1
0
def delete(pk):
    chapter = Chapter.get(id=pk)
    if not chapter:
        abort(404)
    user = current_user._get_current_object()
    if not chapter.story.bl.editable_by(user):
        abort(403)

    story = chapter.story

    if request.method == 'POST':
        chapter.bl.delete(editor=user)
        return redirect(url_for('story.edit', pk=story.id))

    page_title = 'Подтверждение удаления главы'
    data = {
        'page_title': page_title,
        'story': story,
        'chapter': chapter,
    }

    if g.is_ajax:
        html = render_template('includes/ajax/chapter_ajax_confirm_delete.html', page_title=page_title, story=story, chapter=chapter)
        return jsonify({'page_content': {'modal': html, 'title': page_title}})
    return render_template('chapter_confirm_delete.html', **data)
Exemplo n.º 2
0
def hidelinter(pk):
    chapter = Chapter.get(id=pk)
    if not chapter:
        abort(404)
    user = current_user._get_current_object()
    if not chapter.story.bl.editable_by(user):
        abort(403)

    if request.method == 'POST':
        error_codes = request.form.get('errors')
        if error_codes:
            try:
                error_codes = [x.strip() for x in error_codes.split(',')]
                error_codes = [int(x) for x in error_codes if x.isdigit() and len(x) < 4]
            except Exception:
                error_codes = []
        else:
            error_codes = []

        error_codes = set(error_codes)
        if len(error_codes) > 1024:
            error_codes = set()
        user.bl.set_extra('hidden_chapter_linter_errors', list(error_codes))

    return redirect(url_for('chapter.edit', pk=chapter.id))
Exemplo n.º 3
0
def hidelinter(pk):
    chapter = Chapter.get(id=pk)
    if not chapter:
        abort(404)
    user = current_user._get_current_object()
    if not chapter.story.bl.editable_by(user):
        abort(403)

    if request.method == 'POST':
        error_codes = request.form.get('errors')
        if error_codes:
            try:
                error_codes = [x.strip() for x in error_codes.split(',')]
                error_codes = [
                    int(x) for x in error_codes if x.isdigit() and len(x) < 4
                ]
            except Exception:
                error_codes = []
        else:
            error_codes = []

        error_codes = set(error_codes)
        if len(error_codes) > 1024:
            error_codes = set()
        user.bl.set_extra('hidden_chapter_linter_errors', list(error_codes))

    return redirect(url_for('chapter.edit', pk=chapter.id))
Exemplo n.º 4
0
def delete(pk):
    chapter = Chapter.get(id=pk)
    if not chapter:
        abort(404)
    user = current_user._get_current_object()
    if not chapter.story.bl.editable_by(user):
        abort(403)

    story = chapter.story

    if request.method == 'POST':
        chapter.bl.delete(editor=user)
        return redirect(url_for('story.edit', pk=story.id))

    page_title = 'Подтверждение удаления главы'
    data = {
        'page_title': page_title,
        'story': story,
        'chapter': chapter,
    }

    if g.is_ajax:
        html = render_template(
            'includes/ajax/chapter_ajax_confirm_delete.html',
            page_title=page_title,
            story=story,
            chapter=chapter)
        return jsonify({'page_content': {'modal': html, 'title': page_title}})
    return render_template('chapter_confirm_delete.html', **data)
Exemplo n.º 5
0
def publish(pk):
    chapter = Chapter.get(id=pk)
    if not chapter:
        abort(404)
    user = current_user._get_current_object()
    if not chapter.story.bl.editable_by(user):
        abort(403)

    chapter.bl.publish(user, chapter.draft)  # draft == not published
    if g.is_ajax:
        return jsonify({'success': True, 'story_id': chapter.story.id, 'chapter_id': chapter.id, 'published': not chapter.draft})
    return redirect(url_for('story.edit', pk=chapter.story.id))
Exemplo n.º 6
0
def view(story_id, chapter_order=None):
    story = get_story(story_id)
    user = current_user._get_current_object()

    allow_draft = user.is_staff or story.bl.is_contributor(user)

    if chapter_order is not None:
        chapter = Chapter.get(story=story_id, order=chapter_order)
        if not chapter:
            abort(404)
        if chapter.draft and not allow_draft:
            abort(404)
        page_title = chapter.autotitle[:80] + ' : ' + story.title
        prev_chapter = chapter.get_prev_chapter(allow_draft)
        next_chapter = chapter.get_next_chapter(allow_draft)
        if user.is_authenticated:
            chapter.bl.viewed(user)
        data = {
            'story': story,
            'chapter': chapter,
            'prev_chapter': prev_chapter,
            'next_chapter': next_chapter,
            'is_last_chapter': not next_chapter,
            'page_title': page_title,
            'allchapters': False,
            'robots_noindex': not story.published or story.robots_noindex,
        }
    else:
        chapters = list(
            story.bl.select_accessible_chapters(user).order_by(
                Chapter.order, Chapter.id))
        page_title = story.title + ' – все главы'
        if user.is_authenticated:
            for c in chapters:
                c.bl.viewed(user)
        data = {
            'story': story,
            'chapters': chapters,
            'page_title': page_title,
            'allchapters': True,
            'robots_noindex': not story.published or story.robots_noindex,
        }

    response = Response(render_template('chapter_view.html', **data))
    if (current_app.config['CHAPTER_HTML_FRONTEND_CACHE_TIME'] is not None
            and not story.bl.editable_by(current_user)):
        response.cache_control.max_age = current_app.config[
            'CHAPTER_HTML_FRONTEND_CACHE_TIME']
        response.cache_control.private = True
        response.cache_control_exempt = True
    return response
Exemplo n.º 7
0
def check_chapter_views_count(verbosity=0, dry_run=False):
    last_id = None
    all_count = 0
    changed_count = 0

    while True:
        with orm.db_session:
            chapters = Chapter.select().order_by(Chapter.id)
            if last_id is not None:
                chapters = chapters.filter(lambda x: x.id > last_id)
            chapters = list(chapters[:150])
            if not chapters:
                break
            last_id = chapters[-1].id

            for chapter in chapters:
                all_count += 1
                changed = False

                views = set(
                    orm.select(x.author.id for x in StoryView
                               if x.chapter == chapter))

                data = {
                    'views': len(views),
                }

                for k, v in data.items():
                    if verbosity >= 2 or (verbosity
                                          and v != getattr(chapter, k)):
                        print('Chapter {}/{} ({}) {}: {} -> {}'.format(
                            chapter.story.id,
                            chapter.order,
                            chapter.id,
                            k,
                            getattr(chapter, k),
                            v,
                        ),
                              file=sys.stderr)
                    if v != getattr(chapter, k):
                        if not dry_run:
                            setattr(chapter, k, v)
                        changed = True

                if changed:
                    changed_count += 1

    if verbosity >= 1:
        print('{} chapters available, {} chapters changed'.format(
            all_count, changed_count),
              file=sys.stderr)
Exemplo n.º 8
0
def publish(pk):
    story = Story.get(id=pk)
    if not story:
        abort(404)

    user = current_user._get_current_object()
    if not user.is_staff and not story.bl.publishable_by(user):
        abort(403)

    modal = None

    data = {
        'story':
        story,
        'need_words':
        current_app.config['PUBLISH_SIZE_LIMIT'],
        'unpublished_chapters_count':
        Chapter.select(lambda x: x.story == story and x.draft).count(),
    }

    if story.publishing_blocked_until and story.publishing_blocked_until > datetime.utcnow(
    ):
        data['page_title'] = 'Неудачная попытка публикации'
        modal = render_template(
            'includes/ajax/story_ajax_publish_blocked.html', **data)

    elif not story.bl.publish(user, story.draft):  # draft == not published
        data['page_title'] = 'Неудачная попытка публикации'
        modal = render_template(
            'includes/ajax/story_ajax_publish_warning.html', **data)

    elif not story.draft:
        if story.approved:
            data['page_title'] = 'Рассказ опубликован'
            modal = render_template(
                'includes/ajax/story_ajax_publish_approved.html', **data)
        else:
            data['page_title'] = 'Рассказ отправлен на модерацию'
            modal = render_template(
                'includes/ajax/story_ajax_publish_unapproved.html', **data)

    if g.is_ajax:
        return jsonify({
            'success': True,
            'story_id': story.id,
            'published': not story.draft,
            'modal': modal
        })
    return redirect(url_for('story.view',
                            pk=story.id))  # TODO: add warning here too
Exemplo n.º 9
0
def publish(pk):
    chapter = Chapter.get(id=pk)
    if not chapter:
        abort(404)
    user = current_user._get_current_object()
    if not chapter.story.bl.editable_by(user):
        abort(403)

    chapter.bl.publish(user, chapter.draft)  # draft == not published
    if g.is_ajax:
        return jsonify({
            'success': True,
            'story_id': chapter.story.id,
            'chapter_id': chapter.id,
            'published': not chapter.draft
        })
    return redirect(url_for('story.edit', pk=chapter.story.id))
Exemplo n.º 10
0
def view(story_id, chapter_order=None):
    story = get_story(story_id)
    user = current_user._get_current_object()

    allow_draft = user.is_staff or story.bl.is_contributor(user)

    if chapter_order is not None:
        chapter = Chapter.get(story=story_id, order=chapter_order)
        if not chapter:
            abort(404)
        if chapter.draft and not allow_draft:
            abort(404)
        page_title = chapter.autotitle[:80] + ' : ' + story.title
        prev_chapter = chapter.get_prev_chapter(allow_draft)
        next_chapter = chapter.get_next_chapter(allow_draft)
        if user.is_authenticated:
            chapter.bl.viewed(user)
        data = {
            'story': story,
            'chapter': chapter,
            'prev_chapter': prev_chapter,
            'next_chapter': next_chapter,
            'is_last_chapter': not next_chapter,
            'page_title': page_title,
            'allchapters': False,
            'robots_noindex': not story.published or story.robots_noindex,
        }
    else:
        chapters = list(story.bl.select_accessible_chapters(user).order_by(Chapter.order, Chapter.id))
        page_title = story.title + ' – все главы'
        if user.is_authenticated:
            for c in chapters:
                c.bl.viewed(user)
        data = {
            'story': story,
            'chapters': chapters,
            'page_title': page_title,
            'allchapters': True,
            'robots_noindex': not story.published or story.robots_noindex,
        }

    return render_template('chapter_view.html', **data)
Exemplo n.º 11
0
def check_chapter_views_count(verbosity=0, dry_run=False):
    last_id = None
    all_count = 0
    changed_count = 0

    while True:
        with orm.db_session:
            chapters = Chapter.select().order_by(Chapter.id)
            if last_id is not None:
                chapters = chapters.filter(lambda x: x.id > last_id)
            chapters = list(chapters[:150])
            if not chapters:
                break
            last_id = chapters[-1].id

            for chapter in chapters:
                all_count += 1
                changed = False

                views = set(orm.select(x.author.id for x in StoryView if x.chapter == chapter))

                data = {
                    'views': len(views),
                }

                for k, v in data.items():
                    if verbosity >= 2 or (verbosity and v != getattr(chapter, k)):
                        print('Chapter {}/{} ({}) {}: {} -> {}'.format(
                            chapter.story.id, chapter.order, chapter.id, k, getattr(chapter, k), v,
                        ), file=sys.stderr)
                    if v != getattr(chapter, k):
                        if not dry_run:
                            setattr(chapter, k, v)
                        changed = True

                if changed:
                    changed_count += 1

    if verbosity >= 1:
        print('{} chapters available, {} chapters changed'.format(all_count, changed_count), file=sys.stderr)
Exemplo n.º 12
0
def publish(pk):
    story = Story.get(id=pk)
    if not story:
        abort(404)

    user = current_user._get_current_object()
    if not user.is_staff and not story.bl.publishable_by(user):
        abort(403)

    modal = None

    data = {
        'story': story,
        'need_words': current_app.config['PUBLISH_SIZE_LIMIT'],
        'unpublished_chapters_count': Chapter.select(lambda x: x.story == story and x.draft).count(),
    }

    if story.publishing_blocked_until and story.publishing_blocked_until > datetime.utcnow():
        data['page_title'] = 'Неудачная попытка публикации'
        modal = render_template('includes/ajax/story_ajax_publish_blocked.html', **data)

    elif not story.bl.publish(user, story.draft):  # draft == not published
        data['page_title'] = 'Неудачная попытка публикации'
        modal = render_template('includes/ajax/story_ajax_publish_warning.html', **data)

    elif not story.draft:
        if story.approved:
            data['page_title'] = 'Рассказ опубликован'
            modal = render_template('includes/ajax/story_ajax_publish_approved.html', **data)
        else:
            data['page_title'] = 'Рассказ отправлен на модерацию'
            modal = render_template('includes/ajax/story_ajax_publish_unapproved.html', **data)

    if g.is_ajax:
        return jsonify({'success': True, 'story_id': story.id, 'published': not story.draft, 'modal': modal})
    return redirect(url_for('story.view', pk=story.id))  # TODO: add warning here too
Exemplo n.º 13
0
def edit(pk):
    if request.method == 'POST':
        chapter = Chapter.get_for_update(id=pk)
    else:
        chapter = Chapter.get(id=pk)
    if not chapter:
        abort(404)

    user = current_user._get_current_object()
    if not chapter.story.bl.editable_by(user):
        abort(403)

    # Параметром ?l=1 просят запустить линтер.
    # Если всё хорошо, то продолжаем дальше.
    # Если плохо, перенаправляем на страницу с описанием ошибок
    lint_ok = None
    if request.method == 'GET' and request.args.get('l') == '1':
        linter = create_chapter_linter(chapter.text)
        error_codes = set(linter.lint() if linter else [])
        # Те ошибки, которые пользователь просил не показывать, показываем,
        # потому что они явно запрошены параметром ?l=1
        if error_codes:
            redir_url = url_for('chapter.edit', pk=chapter.id)
            redir_url += '?lint={}'.format(_encode_linter_codes(error_codes))
            return redirect(redir_url)
        lint_ok = linter is not None

    chapter_data = {
        'title': chapter.title,
        'notes': chapter.notes,
        'text': chapter.text,
    }

    saved = False
    not_saved = False
    older_text = None
    chapter_text_diff = []

    preview_data = {'preview_title': None, 'preview_html': None, 'notes_preview_html': None}

    form = ChapterForm(data=chapter_data)

    if request.form.get('act') in ('preview', 'preview_selected'):
        preview_data = _gen_preview(request.form, only_selected=request.form.get('act') == 'preview_selected')

        if request.form.get('ajax') == '1':
            return jsonify(
                success=True,
                html=render_template(
                    'includes/chapter_preview.html',
                    story=chapter.story,
                    chapter=chapter,
                    **preview_data
                )
            )

    elif form.validate_on_submit():
        if request.form.get('older_md5'):
            older_text, chapter_text_diff = chapter.bl.get_diff_from_older_version(request.form['older_md5'])
        if not chapter_text_diff:
            chapter.bl.update(
                editor=user,
                data={
                    'title': form.title.data,
                    'notes': form.notes.data,
                    'text': form.text.data,
                }
            )
            saved = True
        else:
            form.text.errors.append(
                'Пока вы редактировали главу, её отредактировал кто-то другой. Ниже представлен '
                'список изменений, которые вы пропустили. Перенесите их в свой текст и сохраните '
                'главу ещё раз.'
            )
            not_saved = True
    elif request.method == 'POST':
        not_saved = True

    # Если мы дошли сюда, значит рисуем форму редактирования главы.
    # В параметре lint могли запросить описание ошибок линтера,
    # запрашиваем их из собственно линтера
    error_codes = []
    linter_error_messages = {}
    linter_allow_hide = ''
    if request.method == 'GET' and request.args.get('lint'):
        error_codes = set(_decode_linter_codes(request.args['lint']))
    if error_codes:
        linter = create_chapter_linter(None)
        linter_error_messages = linter.get_error_messages(error_codes) if linter else []

        # Интересуемся, отображались эти ошибки раньше. Если ни одной новой,
        # разрешаем пользователя попросить больше не показывать
        old_error_codes = set(user.bl.get_extra('shown_chapter_linter_errors') or [])
        if not (error_codes - old_error_codes):
            linter_allow_hide = ','.join(str(x) for x in (error_codes | set(user.bl.get_extra('hidden_chapter_linter_errors') or [])))
        else:
            user.bl.set_extra('shown_chapter_linter_errors', list(error_codes | old_error_codes))

    data = {
        'page_title': 'Редактирование главы «%s»' % chapter.autotitle,
        'story': chapter.story,
        'chapter': chapter,
        'form': form,
        'edit': True,
        'saved': saved,
        'not_saved': not_saved,
        'chapter_text_diff': chapter_text_diff,
        'diff_html': diff2html(older_text, chapter_text_diff) if chapter_text_diff else None,
        'unpublished_chapters_count': Chapter.select(lambda x: x.story == chapter.story and x.draft).count(),
        'linter_error_messages': linter_error_messages,
        'lint_ok': lint_ok,
        'linter_allow_hide': linter_allow_hide,
    }
    data.update(preview_data)

    result = current_app.make_response(render_template('chapter_work.html', **data))
    if saved:
        result.set_cookie('formsaving_clear', 'chapter', max_age=None)
    return result
Exemplo n.º 14
0
def add(story_id):
    if request.method == 'POST':
        story = Story.get_for_update(id=story_id)
    else:
        story = Story.get(id=story_id)
    if not story:
        abort(404)
    user = current_user._get_current_object()
    if not story.bl.editable_by(user):
        abort(403)

    not_saved = False

    preview_data = {'preview_title': None, 'preview_html': None, 'notes_preview_html': None}

    form = ChapterForm()

    if request.form.get('act') in ('preview', 'preview_selected'):
        preview_data = _gen_preview(request.form, only_selected=request.form.get('act') == 'preview_selected')

        if request.form.get('ajax') == '1':
            return jsonify(
                success=True,
                html=render_template(
                    'includes/chapter_preview.html',
                    story=story,
                    chapter=None,
                    **preview_data
                )
            )

    elif form.validate_on_submit():
        chapter = Chapter.bl.create(
            story=story,
            editor=user,
            data={
                'title': form.title.data,
                'notes': form.notes.data,
                'text': form.text.data,
            },
        )
        if request.form.get('act') == 'publish':
            story.bl.publish_all_chapters(user)

        redir_url = url_for('chapter.edit', pk=chapter.id)

        # Запускаем поиск распространённых ошибок в тексте главы
        linter = create_chapter_linter(chapter.text)
        error_codes = set(linter.lint() if linter else [])

        # Те ошибки, которые пользователь просил не показывать, не показываем
        error_codes = error_codes - set(user.bl.get_extra('hidden_chapter_linter_errors') or [])

        if error_codes:
            redir_url += '?lint={}'.format(_encode_linter_codes(error_codes))
        result = redirect(redir_url)
        result.set_cookie('formsaving_clear', 'chapter', max_age=None)
        return result

    elif request.method == 'POST':
        not_saved = True

    data = {
        'page_title': 'Добавление новой главы',
        'story': story,
        'chapter': None,
        'form': form,
        'saved': False,
        'not_saved': not_saved,
        'unpublished_chapters_count': Chapter.select(lambda x: x.story == story and x.draft).count(),
    }
    data.update(preview_data)

    return render_template('chapter_work.html', **data)
Exemplo n.º 15
0
def edit(pk):
    if request.method == 'POST':
        chapter = Chapter.get_for_update(id=pk)
    else:
        chapter = Chapter.get(id=pk)
    if not chapter:
        abort(404)

    user = current_user._get_current_object()
    if not chapter.story.bl.editable_by(user):
        abort(403)

    # Параметром ?l=1 просят запустить линтер.
    # Если всё хорошо, то продолжаем дальше.
    # Если плохо, перенаправляем на страницу с описанием ошибок
    lint_ok = None
    if request.method == 'GET' and request.args.get('l') == '1':
        linter = create_chapter_linter(chapter.text)
        error_codes = set(linter.lint() if linter else [])
        # Те ошибки, которые пользователь просил не показывать, показываем,
        # потому что они явно запрошены параметром ?l=1
        if error_codes:
            redir_url = url_for('chapter.edit', pk=chapter.id)
            redir_url += '?lint={}'.format(_encode_linter_codes(error_codes))
            return redirect(redir_url)
        lint_ok = linter is not None

    chapter_data = {
        'title': chapter.title,
        'notes': chapter.notes,
        'text': chapter.text,
    }

    saved = False
    not_saved = False
    older_text = None
    chapter_text_diff = []

    preview_data = {
        'preview_title': None,
        'preview_html': None,
        'notes_preview_html': None
    }

    form = ChapterForm(data=chapter_data)

    if request.form.get('act') in ('preview', 'preview_selected'):
        preview_data = _gen_preview(
            request.form,
            only_selected=request.form.get('act') == 'preview_selected')

        if request.form.get('ajax') == '1':
            return jsonify(success=True,
                           html=render_template(
                               'includes/chapter_preview.html',
                               story=chapter.story,
                               chapter=chapter,
                               **preview_data))

    elif form.validate_on_submit():
        if request.form.get('older_md5'):
            older_text, chapter_text_diff = chapter.bl.get_diff_from_older_version(
                request.form['older_md5'])
        if not chapter_text_diff:
            chapter.bl.update(editor=user,
                              data={
                                  'title': form.title.data,
                                  'notes': form.notes.data,
                                  'text': form.text.data,
                              })
            saved = True
        else:
            form.text.errors.append(
                'Пока вы редактировали главу, её отредактировал кто-то другой. Ниже представлен '
                'список изменений, которые вы пропустили. Перенесите их в свой текст и сохраните '
                'главу ещё раз.')
            not_saved = True
    elif request.method == 'POST':
        not_saved = True

    # Если мы дошли сюда, значит рисуем форму редактирования главы.
    # В параметре lint могли запросить описание ошибок линтера,
    # запрашиваем их из собственно линтера
    error_codes = []
    linter_error_messages = {}
    linter_allow_hide = ''
    if request.method == 'GET' and request.args.get('lint'):
        error_codes = set(_decode_linter_codes(request.args['lint']))
    if error_codes:
        linter = create_chapter_linter(None)
        linter_error_messages = linter.get_error_messages(
            error_codes) if linter else []

        # Интересуемся, отображались эти ошибки раньше. Если ни одной новой,
        # разрешаем пользователя попросить больше не показывать
        old_error_codes = set(
            user.bl.get_extra('shown_chapter_linter_errors') or [])
        if not (error_codes - old_error_codes):
            linter_allow_hide = ','.join(
                str(x) for x in (error_codes | set(
                    user.bl.get_extra('hidden_chapter_linter_errors') or [])))
        else:
            user.bl.set_extra('shown_chapter_linter_errors',
                              list(error_codes | old_error_codes))

    data = {
        'page_title':
        'Редактирование главы «%s»' % chapter.autotitle,
        'story':
        chapter.story,
        'chapter':
        chapter,
        'form':
        form,
        'edit':
        True,
        'saved':
        saved,
        'not_saved':
        not_saved,
        'chapter_text_diff':
        chapter_text_diff,
        'diff_html':
        diff2html(older_text, chapter_text_diff)
        if chapter_text_diff else None,
        'unpublished_chapters_count':
        Chapter.select(lambda x: x.story == chapter.story and x.draft).count(),
        'linter_error_messages':
        linter_error_messages,
        'lint_ok':
        lint_ok,
        'linter_allow_hide':
        linter_allow_hide,
    }
    data.update(preview_data)

    result = current_app.make_response(
        render_template('chapter_work.html', **data))
    if saved:
        result.set_cookie('formsaving_clear', 'chapter', max_age=None)
    return result
Exemplo n.º 16
0
def chapters_updates(params):
    # Старая логика, при которой могли выводиться много глав одного рассказа подряд
    # chapters = select(c for c in Chapter if not c.draft and c.story_published and c.order != 1)
    # chapters = chapters.order_by(Chapter.first_published_at.desc(), Chapter.order.desc())
    # chapters = chapters[:current_app.config['CHAPTERS_COUNT']['main']]
    # story_ids = [y.story.id for y in chapters]
    # chapters_stories = select(x for x in Story if x.id in story_ids).prefetch(Story.contributors, StoryContributor.user)[:]
    # chapters_stories = {x.id: x for x in chapters_stories}
    # chapters = [(x, chapters_stories[x.story.id]) for x in chapters]

    chapters = current_app.cache.get('index_updated_chapters')
    if chapters is None:
        # Забираем id последних обновлённых рассказов
        # (главы не берём, так как у одного рассказа их может быть много, а нам нужна всего одна)
        index_updated_story_ids = select(
            (c.story.id, max(c.first_published_at)) for c in Chapter
            if not c.draft and c.story_published and c.order != 1)
        index_updated_story_ids = [
            x[0] for x in index_updated_story_ids.order_by(-2)
            [:current_app.config['CHAPTERS_COUNT']['main']]
        ]

        # Забираем последнюю главу каждого рассказа
        # (TODO: наверняка можно оптимизировать, но не придумалось как)
        latest_chapters = list(
            select((c.story.id, c.id, c.first_published_at, c.order)
                   for c in Chapter if not c.draft and c.story_published
                   and c.story.id in index_updated_story_ids).order_by(-3, -4))

        index_updated_chapter_ids = []
        for story_id in index_updated_story_ids:
            for x in latest_chapters:
                if x[0] == story_id:
                    index_updated_chapter_ids.append(x[1])
                    break
        assert len(index_updated_chapter_ids) == len(index_updated_story_ids)

        chapters_objs = Chapter.select(
            lambda x: x.id in index_updated_chapter_ids)
        chapters_objs = {
            x.id: x
            for x in chapters_objs.prefetch(Chapter.story, Story.contributors,
                                            StoryContributor.user)
        }

        # Переводим в более простой формат, близкий к json, чтоб удобнее кэшировать и задел на будущие переделки
        # И попутно сортировка
        chapters = []
        for chapter_id in index_updated_chapter_ids:
            x = chapters_objs[chapter_id]
            chapters.append({
                'id': x.id,
                'order': x.order,
                'title': x.title,
                'autotitle': x.autotitle,
                'first_published_at': x.first_published_at,
                'story': {
                    'id':
                    x.story.id,
                    'title':
                    x.story.title,
                    'first_published_at':
                    x.story.first_published_at,
                    'updated':
                    x.story.updated,
                    'authors': [{
                        'id': a.id,
                        'username': a.username,
                    } for a in x.story.authors]
                }
            })
        current_app.cache.set('index_updated_chapters', chapters, 600)

    # Число непрочитанных глав у текущего пользователя
    if current_user.is_authenticated:
        unread_chapters_count = Story.bl.get_unread_chapters_count(
            current_user._get_current_object(),
            [x['story']['id'] for x in chapters])
    else:
        unread_chapters_count = {x['story']['id']: 0 for x in chapters}

    return render_template('sidebar/chapters_updates.html',
                           chapters=chapters,
                           unread_chapters_count=unread_chapters_count)
Exemplo n.º 17
0
def sphinx_update_chapter(chapter_id, update_story_words=True):
    chapter = Chapter.get(id=chapter_id)
    if not chapter:
        return

    chapter.bl.search_add(update_story_words=update_story_words)
Exemplo n.º 18
0
    def get_notifications(self, older=None, offset=0, count=50):
        from mini_fiction.models import Notification, Story, Chapter, StoryComment, StoryLocalThread, StoryLocalComment, NewsComment

        user = self.model

        if older is None and offset == 0 and count <= 101:
            result = current_app.cache.get('bell_content_{}'.format(user.id))
            if result is not None:
                return result[:count]

        result = []

        # Забираем уведомления
        items = user.notifications.filter(
            lambda x: x.id < older
        ) if older is not None else user.notifications
        items = items.order_by(Notification.id.desc()).prefetch(
            Notification.caused_by_user)[offset:offset + count]

        # Группируем таргеты по типам, чтобы брать их одним sql-запросом
        story_ids = set()
        chapter_ids = set()
        story_comment_ids = set()
        local_comment_ids = set()
        news_comment_ids = set()
        for n in items:
            if n.type in ('story_publish', 'story_draft', 'author_story'):
                story_ids.add(n.target_id)
            elif n.type == 'story_chapter':
                chapter_ids.add(n.target_id)
            elif n.type in ('story_reply', 'story_comment'):
                story_comment_ids.add(n.target_id)
            elif n.type in ('story_lreply', 'story_lcomment'):
                local_comment_ids.add(n.target_id)
            elif n.type in ('news_reply', 'news_comment'):
                news_comment_ids.add(n.target_id)

        # И забираем все эти таргеты
        stories = {x.id: x
                   for x in Story.select(lambda x: x.id in story_ids)
                   } if story_ids else {}

        chapters = {
            x.id: x
            for x in Chapter.select(lambda x: x.id in chapter_ids)
        } if chapter_ids else {}

        story_comments = {
            x.id: x
            for x in StoryComment.select(lambda x: x.id in story_comment_ids).
            prefetch(StoryComment.story)
        } if story_comment_ids else {}

        local_comments = {
            x.id: x
            for x in StoryLocalComment.select(
                lambda x: x.id in local_comment_ids).prefetch(
                    StoryLocalComment.local, StoryLocalThread.story)
        } if local_comment_ids else {}

        news_comments = {
            x.id: x
            for x in NewsComment.select(lambda x: x.id in news_comment_ids).
            prefetch(NewsComment.newsitem)
        } if news_comment_ids else {}

        # Преобразуем каждое уведомление в json-совместимый формат со всеми дополнениями
        for n in items:
            item = {
                'id': n.id,
                'created_at': n.created_at,
                'type': n.type,
                'viewed': n.id <= user.last_viewed_notification_id,
                'user': {
                    'id': n.caused_by_user.id,
                    'username': n.caused_by_user.username,
                    'is_staff': n.caused_by_user.is_staff,
                } if n.caused_by_user else None,
                'extra': json.loads(n.extra or '{}'),
            }

            if n.type in ('story_publish', 'story_draft', 'author_story'):
                if n.target_id not in stories:
                    item['broken'] = True
                    result.append(item)
                    continue
                item['story'] = {
                    'id': n.target_id,
                    'title': stories[n.target_id].title,
                }
                if n.type == 'author_story':
                    item['story']['words'] = stories[n.target_id].words
                    item['story']['authors'] = [{
                        'id': x.id,
                        'username': x.username
                    } for x in stories[n.target_id].authors]

            elif n.type == 'story_chapter':
                c = chapters.get(n.target_id)
                if not c:
                    item['broken'] = True
                    result.append(item)
                    continue

                item['story'] = {'id': c.story.id, 'title': c.story.title}
                item['chapter'] = {
                    'id': c.id,
                    'order': c.order,
                    'title': c.title,
                    'autotitle': c.autotitle,
                    'words': c.words
                }

            elif n.type in ('story_reply', 'story_comment', 'story_lreply',
                            'story_lcomment', 'news_reply', 'news_comment'):
                if n.type in ('story_reply', 'story_comment'):
                    c = story_comments.get(n.target_id)
                elif n.type in ('story_lreply', 'story_lcomment'):
                    c = local_comments.get(n.target_id)
                elif n.type in ('news_reply', 'news_comment'):
                    c = news_comments.get(n.target_id)

                if not c:
                    item['broken'] = True
                    result.append(item)
                    continue

                if c.deleted and not user.is_staff:
                    item['comment'] = {
                        'id': c.id,
                        'local_id': c.local_id,
                        'permalink': c.bl.get_permalink(),
                        'can_vote': c.bl.can_vote,
                        'deleted': True,
                    }
                else:
                    item['comment'] = {
                        'id': c.id,
                        'local_id': c.local_id,
                        'permalink': c.bl.get_permalink(),
                        'date': c.date,
                        'brief_text_as_html': str(c.brief_text_as_html),
                        'can_vote': c.bl.can_vote,
                        'deleted': c.deleted,
                        'author': {
                            'id':
                            c.author.id if c.author else None,
                            'username':
                            c.author.username
                            if c.author else c.author_username,
                        }
                    }
                    if hasattr(c, 'vote_total'):
                        item['comment']['vote_total'] = c.vote_total

                if n.type in ('story_reply', 'story_comment'):
                    item['story'] = {'id': c.story.id, 'title': c.story.title}
                elif n.type in ('story_lreply', 'story_lcomment'):
                    item['story'] = {
                        'id': c.local.story.id,
                        'title': c.local.story.title
                    }
                elif n.type in ('news_reply', 'news_comment'):
                    item['newsitem'] = {
                        'id': c.newsitem.id,
                        'name': c.newsitem.name,
                        'title': c.newsitem.title
                    }

            result.append(item)

        if older is None and offset == 0 and count >= 101:
            current_app.cache.set('bell_content_{}'.format(user.id),
                                  result[:101], 600)
        return result
Exemplo n.º 19
0
def chapters_updates(params):
    # Старая логика, при которой могли выводиться много глав одного рассказа подряд
    # chapters = select(c for c in Chapter if not c.draft and c.story_published and c.order != 1)
    # chapters = chapters.order_by(Chapter.first_published_at.desc(), Chapter.order.desc())
    # chapters = chapters[:current_app.config['CHAPTERS_COUNT']['main']]
    # story_ids = [y.story.id for y in chapters]
    # chapters_stories = select(x for x in Story if x.id in story_ids).prefetch(Story.contributors, StoryContributor.user)[:]
    # chapters_stories = {x.id: x for x in chapters_stories}
    # chapters = [(x, chapters_stories[x.story.id]) for x in chapters]

    chapters = current_app.cache.get('index_updated_chapters')
    if chapters is None:
        # Забираем id последних обновлённых рассказов
        # (главы не берём, так как у одного рассказа их может быть много, а нам нужна всего одна)
        index_updated_story_ids = select((c.story.id, max(c.first_published_at)) for c in Chapter if not c.draft and c.story_published and c.order != 1)
        index_updated_story_ids = [x[0] for x in index_updated_story_ids.order_by(-2)[:current_app.config['CHAPTERS_COUNT']['main']]]

        # Забираем последнюю главу каждого рассказа
        # (TODO: наверняка можно оптимизировать, но не придумалось как)
        latest_chapters = list(select(
            (c.story.id, c.id, c.first_published_at, c.order)
            for c in Chapter
            if not c.draft and c.story_published and c.story.id in index_updated_story_ids
        ).order_by(-3, -4))

        index_updated_chapter_ids = []
        for story_id in index_updated_story_ids:
            for x in latest_chapters:
                if x[0] == story_id:
                    index_updated_chapter_ids.append(x[1])
                    break
        assert len(index_updated_chapter_ids) == len(index_updated_story_ids)

        chapters_objs = Chapter.select(lambda x: x.id in index_updated_chapter_ids)
        chapters_objs = {x.id: x for x in chapters_objs.prefetch(Chapter.story, Story.contributors, StoryContributor.user)}

        # Переводим в более простой формат, близкий к json, чтоб удобнее кэшировать и задел на будущие переделки
        # И попутно сортировка
        chapters = []
        for chapter_id in index_updated_chapter_ids:
            x = chapters_objs[chapter_id]
            chapters.append({
                'id': x.id,
                'order': x.order,
                'title': x.title,
                'autotitle': x.autotitle,
                'first_published_at': x.first_published_at,
                'story': {
                    'id': x.story.id,
                    'title': x.story.title,
                    'first_published_at': x.story.first_published_at,
                    'updated': x.story.updated,
                    'authors': [{
                        'id': a.id,
                        'username': a.username,
                    } for a in x.story.authors]
                }
            })
        current_app.cache.set('index_updated_chapters', chapters, 600)

    # Число непрочитанных глав у текущего пользователя
    if current_user.is_authenticated:
        unread_chapters_count = Story.bl.get_unread_chapters_count(
            current_user._get_current_object(), [x['story']['id'] for x in chapters]
        )
    else:
        unread_chapters_count = {x['story']['id']: 0 for x in chapters}

    return render_template('sidebar/chapters_updates.html', chapters=chapters, unread_chapters_count=unread_chapters_count)
Exemplo n.º 20
0
    def get_notifications(self, older=None, offset=0, count=50):
        from mini_fiction.models import Notification, Story, Chapter, StoryComment, StoryLocalThread, StoryLocalComment, NewsComment

        user = self.model

        if older is None and offset == 0 and count <= 101:
            result = current_app.cache.get('bell_content_{}'.format(user.id))
            if result is not None:
                return result[:count]

        result = []

        # Забираем уведомления
        items = user.notifications.filter(lambda x: x.id < older) if older is not None else user.notifications
        items = items.order_by(Notification.id.desc()).prefetch(Notification.caused_by_user)[offset:offset + count]

        # Группируем таргеты по типам, чтобы брать их одним sql-запросом
        story_ids = set()
        chapter_ids = set()
        story_comment_ids = set()
        local_comment_ids = set()
        news_comment_ids = set()
        for n in items:
            if n.type in ('story_publish', 'story_draft', 'author_story'):
                story_ids.add(n.target_id)
            elif n.type == 'story_chapter':
                chapter_ids.add(n.target_id)
            elif n.type in ('story_reply', 'story_comment'):
                story_comment_ids.add(n.target_id)
            elif n.type in ('story_lreply', 'story_lcomment'):
                local_comment_ids.add(n.target_id)
            elif n.type in ('news_reply', 'news_comment'):
                news_comment_ids.add(n.target_id)

        # И забираем все эти таргеты
        stories = {
            x.id: x for x in Story.select(
                lambda x: x.id in story_ids
            )
        } if story_ids else {}

        chapters = {
            x.id: x for x in Chapter.select(
                lambda x: x.id in chapter_ids
            )
        } if chapter_ids else {}

        story_comments = {
            x.id: x for x in StoryComment.select(
                lambda x: x.id in story_comment_ids
            ).prefetch(StoryComment.story)
        } if story_comment_ids else {}

        local_comments = {
            x.id: x for x in StoryLocalComment.select(
                lambda x: x.id in local_comment_ids
            ).prefetch(StoryLocalComment.local, StoryLocalThread.story)
        } if local_comment_ids else {}

        news_comments = {
            x.id: x for x in NewsComment.select(
                lambda x: x.id in news_comment_ids
            ).prefetch(NewsComment.newsitem)
        } if news_comment_ids else {}

        # Преобразуем каждое уведомление в json-совместимый формат со всеми дополнениями
        for n in items:
            item = {
                'id': n.id,
                'created_at': n.created_at,
                'type': n.type,
                'viewed': n.id <= user.last_viewed_notification_id,
                'user': {
                    'id': n.caused_by_user.id,
                    'username': n.caused_by_user.username,
                    'is_staff': n.caused_by_user.is_staff,
                } if n.caused_by_user else None,
                'extra': json.loads(n.extra or '{}'),
            }

            if n.type in ('story_publish', 'story_draft', 'author_story'):
                if n.target_id not in stories:
                    item['broken'] = True
                    result.append(item)
                    continue
                item['story'] = {
                    'id': n.target_id,
                    'title': stories[n.target_id].title,
                }
                if n.type == 'author_story':
                    item['story']['words'] = stories[n.target_id].words
                    item['story']['authors'] = [{'id': x.id, 'username': x.username} for x in stories[n.target_id].authors]

            elif n.type == 'story_chapter':
                c = chapters.get(n.target_id)
                if not c:
                    item['broken'] = True
                    result.append(item)
                    continue

                item['story'] = {'id': c.story.id, 'title': c.story.title}
                item['chapter'] = {'id': c.id, 'order': c.order, 'title': c.title, 'autotitle': c.autotitle, 'words': c.words}

            elif n.type in ('story_reply', 'story_comment', 'story_lreply', 'story_lcomment', 'news_reply', 'news_comment'):
                if n.type in ('story_reply', 'story_comment'):
                    c = story_comments.get(n.target_id)
                elif n.type in ('story_lreply', 'story_lcomment'):
                    c = local_comments.get(n.target_id)
                elif n.type in ('news_reply', 'news_comment'):
                    c = news_comments.get(n.target_id)

                if not c:
                    item['broken'] = True
                    result.append(item)
                    continue

                if c.deleted and not user.is_staff:
                    item['comment'] = {
                        'id': c.id,
                        'local_id': c.local_id,
                        'permalink': c.bl.get_permalink(),
                        'can_vote': c.bl.can_vote,
                        'deleted': True,
                    }
                else:
                    item['comment'] = {
                        'id': c.id,
                        'local_id': c.local_id,
                        'permalink': c.bl.get_permalink(),
                        'date': c.date,
                        'brief_text_as_html': str(c.brief_text_as_html),
                        'can_vote': c.bl.can_vote,
                        'deleted': c.deleted,
                        'author': {
                            'id': c.author.id if c.author else None,
                            'username': c.author.username if c.author else c.author_username,
                        }
                    }
                    if hasattr(c, 'vote_total'):
                        item['comment']['vote_total'] = c.vote_total

                if n.type in ('story_reply', 'story_comment'):
                    item['story'] = {'id': c.story.id, 'title': c.story.title}
                elif n.type in ('story_lreply', 'story_lcomment'):
                    item['story'] = {'id': c.local.story.id, 'title': c.local.story.title}
                elif n.type in ('news_reply', 'news_comment'):
                    item['newsitem'] = {'id': c.newsitem.id, 'name': c.newsitem.name, 'title': c.newsitem.title}

            result.append(item)

        if older is None and offset == 0 and count >= 101:
            current_app.cache.set('bell_content_{}'.format(user.id), result[:101], 600)
        return result
Exemplo n.º 21
0
def sphinx_update_chapter(chapter_id, update_story_words=True):
    chapter = Chapter.get(id=chapter_id)
    if not chapter:
        return

    chapter.bl.search_add(update_story_words=update_story_words)
Exemplo n.º 22
0
def initsphinx():
    if current_app.config.get('SPHINX_DISABLED'):
        print('Please set SPHINX_DISABLED = False before initsphinx.',
              file=sys.stderr)
        sys.exit(1)

    orm.sql_debug(False)
    sys.stderr.write(' Cleaning...')
    sys.stderr.flush()
    with current_app.sphinx as sphinx:
        sphinx.delete('stories', id__gte=0)
    with current_app.sphinx as sphinx:
        sphinx.delete('chapters', id__gte=0)
    with current_app.sphinx as sphinx:
        sphinx.flush('stories')
    with current_app.sphinx as sphinx:
        sphinx.flush('chapters')
    sys.stderr.write('\n')
    sys.stderr.flush()

    ok = 0
    pk = 0
    stories = None
    with db_session:
        count = Story.select().count()
    while True:
        with db_session:
            stories = tuple(
                Story.select(lambda x: x.id > pk).prefetch(
                    Story.contributors,
                    Story.characters,
                    # Story.categories,
                    # Story.classifications,
                    Story.tags,
                ).order_by(Story.id)[:100])
            if not stories:
                break

            Story.bl.add_stories_to_search(stories, with_chapters=False)
            pk = stories[-1].id
            ok += len(stories)
            sys.stderr.write(' [%.1f%%] %d/%d stories\r' %
                             (ok * 100 / count, ok, count))
            sys.stderr.flush()

    with current_app.sphinx as sphinx:
        sphinx.flush('stories')

    sys.stderr.write('\n')

    ok = 0
    pk = 0
    chapters = None
    with db_session:
        count = Chapter.select().count()
    while True:
        with db_session:
            chapters = tuple(
                Chapter.select(lambda x: x.id > pk).order_by(Chapter.id)[:20])
            if not chapters:
                break

            Chapter.bl.add_chapters_to_search(chapters,
                                              update_story_words=False)
            pk = chapters[-1].id
            ok += len(chapters)
            sys.stderr.write(' [%.1f%%] %d/%d chapters\r' %
                             (ok * 100 / count, ok, count))
            sys.stderr.flush()

    with current_app.sphinx as sphinx:
        sphinx.flush('chapters')

    sys.stderr.write('\n')
    sys.stderr.flush()
Exemplo n.º 23
0
def add(story_id):
    if request.method == 'POST':
        story = Story.get_for_update(id=story_id)
    else:
        story = Story.get(id=story_id)
    if not story:
        abort(404)
    user = current_user._get_current_object()
    if not story.bl.editable_by(user):
        abort(403)

    not_saved = False

    preview_data = {
        'preview_title': None,
        'preview_html': None,
        'notes_preview_html': None
    }

    form = ChapterForm()

    if request.form.get('act') in ('preview', 'preview_selected'):
        preview_data = _gen_preview(
            request.form,
            only_selected=request.form.get('act') == 'preview_selected')

        if request.form.get('ajax') == '1':
            return jsonify(success=True,
                           html=render_template(
                               'includes/chapter_preview.html',
                               story=story,
                               chapter=None,
                               **preview_data))

    elif form.validate_on_submit():
        chapter = Chapter.bl.create(
            story=story,
            editor=user,
            data={
                'title': form.title.data,
                'notes': form.notes.data,
                'text': form.text.data,
            },
        )
        if request.form.get('act') == 'publish':
            story.bl.publish_all_chapters(user)

        redir_url = url_for('chapter.edit', pk=chapter.id)

        # Запускаем поиск распространённых ошибок в тексте главы
        linter = create_chapter_linter(chapter.text)
        error_codes = set(linter.lint() if linter else [])

        # Те ошибки, которые пользователь просил не показывать, не показываем
        error_codes = error_codes - set(
            user.bl.get_extra('hidden_chapter_linter_errors') or [])

        if error_codes:
            redir_url += '?lint={}'.format(_encode_linter_codes(error_codes))
        result = redirect(redir_url)
        result.set_cookie('formsaving_clear', 'chapter', max_age=None)
        return result

    elif request.method == 'POST':
        not_saved = True

    data = {
        'page_title':
        'Добавление новой главы',
        'story':
        story,
        'chapter':
        None,
        'form':
        form,
        'saved':
        False,
        'not_saved':
        not_saved,
        'unpublished_chapters_count':
        Chapter.select(lambda x: x.story == story and x.draft).count(),
    }
    data.update(preview_data)

    return render_template('chapter_work.html', **data)