Ejemplo n.º 1
0
def update(pk):
    if not pk.isdigit():
        iname = normalize_tag(pk)
        if not iname:
            abort(404)
        tag = Tag.get(iname=iname)
        if not tag:
            abort(404)
        return redirect(url_for('admin_tags.update', pk=str(tag.id)))

    pk = int(pk)
    tag = Tag.get(id=pk)
    if not tag:
        abort(404)

    form = TagForm(
        data={
            'name': tag.name,
            'category': tag.category.id if tag.category else 0,
            'color': tag.color,
            'description': tag.description,
            'is_main_tag': tag.is_main_tag,
            'is_alias_for': tag.is_alias_for.name if tag.is_alias_for else '',
            'is_hidden_alias': tag.is_hidden_alias,
            'reason_to_blacklist': tag.reason_to_blacklist,
        })

    saved = False

    if form.validate_on_submit():
        data = dict(form.data)
        if data.get('category') == 0:
            data['category'] = None
        try:
            tag.bl.update(current_user._get_current_object(), data)
        except ValidationError as exc:
            form.set_errors(exc.errors)
        else:
            saved = True

    tag_aliases = Tag.select(lambda x: x.is_alias_for == tag)
    visible_tag_aliases = [x for x in tag_aliases if not x.is_hidden_alias]
    hidden_tag_aliases = [x for x in tag_aliases if x.is_hidden_alias]

    return render_template(
        'admin/tags/work.html',
        page_title=tag.name,
        tag=tag,
        visible_tag_aliases=visible_tag_aliases,
        hidden_tag_aliases=hidden_tag_aliases,
        form=form,
        edit=True,
        saved=saved,
    )
Ejemplo n.º 2
0
def update(pk):
    if not pk.isdigit():
        iname = normalize_tag(pk)
        if not iname:
            abort(404)
        tag = Tag.get(iname=iname)
        if not tag:
            abort(404)
        return redirect(url_for('admin_tags.update', pk=str(tag.id)))

    pk = int(pk)
    tag = Tag.get(id=pk)
    if not tag:
        abort(404)

    form = TagForm(data={
        'name': tag.name,
        'category': tag.category.id if tag.category else 0,
        'color': tag.color,
        'description': tag.description,
        'is_main_tag': tag.is_main_tag,
        'is_alias_for': tag.is_alias_for.name if tag.is_alias_for else '',
        'is_hidden_alias': tag.is_hidden_alias,
        'reason_to_blacklist': tag.reason_to_blacklist,
    })

    saved = False

    if form.validate_on_submit():
        data = dict(form.data)
        if data.get('category') == 0:
            data['category'] = None
        try:
            tag.bl.update(current_user._get_current_object(), data)
        except ValidationError as exc:
            form.set_errors(exc.errors)
        else:
            saved = True

    tag_aliases = Tag.select(lambda x: x.is_alias_for == tag)
    visible_tag_aliases = [x for x in tag_aliases if not x.is_hidden_alias]
    hidden_tag_aliases = [x for x in tag_aliases if x.is_hidden_alias]

    return render_template(
        'admin/tags/work.html',
        page_title=tag.name,
        tag=tag,
        visible_tag_aliases=visible_tag_aliases,
        hidden_tag_aliases=hidden_tag_aliases,
        form=form,
        edit=True,
        saved=saved,
    )
Ejemplo n.º 3
0
    def create(self, user, data):
        from mini_fiction.models import Tag, AdminLog

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

        data = Validator(TAG).validated(data)

        errors = {}

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

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

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

        if errors:
            raise ValidationError(errors)

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

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

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

        return tag
Ejemplo n.º 4
0
    def get_tags_with_categories(self, sort='name'):
        from mini_fiction.models import Tag

        categories_dict = {}

        tags = list(Tag.select(lambda x: not x.is_blacklisted and not x.is_alias).prefetch(Tag.category))
        if sort == 'stories':
            tags.sort(key=lambda tag: tag.published_stories_count, reverse=True)
        elif sort == 'date':
            tags.sort(key=lambda tag: tag.created_at, reverse=True)
        elif sort == 'name':
            tags.sort(key=lambda tag: tag.iname)
        else:
            raise ValueError('Invalid tags sorting: {!r}'.format(sort))

        categories_dict = {}
        others = {'category': None, 'tags': []}
        for tag in tags:
            if not tag.category:
                others['tags'].append(tag)
                continue
            if tag.category.id not in categories_dict:
                categories_dict[tag.category.id] = {'category': tag.category, 'tags': []}
            categories_dict[tag.category.id]['tags'].append(tag)

        result = sorted(categories_dict.values(), key=lambda x: x['category'].id)
        result.append(others)
        return result
Ejemplo n.º 5
0
def tag_index(tag_name, page):
    iname = normalize_tag(tag_name)
    if not iname:
        abort(404)
    tag = Tag.get(iname=iname)
    if tag and tag.is_alias_for is not None:
        if tag.is_alias_for.is_alias_for:
            raise RuntimeError('Tag alias {} refers to another alias {}!'.format(tag.id, tag.is_alias_for.id))
        tag = tag.is_alias_for
    if not tag or tag.is_blacklisted:
        abort(404)
    if tag.iname != tag_name:
        return redirect(url_for('tags.tag_index', tag_name=tag.iname, page=page))

    objects = Story.bl.select_by_tag(tag, user=current_user._get_current_object())
    objects = objects.prefetch(Story.characters, Story.contributors, StoryContributor.user, Story.tags, StoryTag.tag, Tag.category)
    objects = objects.order_by(Story.first_published_at.desc(), Story.id.desc())

    page_obj = Paginator(page, objects.count(), per_page=current_app.config['STORIES_COUNT']['stream'])
    objects = page_obj.slice_or_404(objects)

    return render_template(
        'tags/tag_index.html',
        page_title=tag.name,
        tag=tag,
        aliases=[x.name for x in Tag.bl.get_aliases_for([tag])[tag.id]],
        category=tag.category,
        stories=objects,
        page_obj=page_obj,
        **cached_lists([x.id for x in objects])
    )
Ejemplo n.º 6
0
    def search_by_prefix(self, name, with_aliases=True, limit=20):
        from mini_fiction.models import Tag

        iname = normalize_tag(name)
        if not iname or limit < 1:
            return []
        if limit > 100:
            limit = 100

        result = []

        # Ищем точное совпадение
        exact_tag = Tag.get(iname=iname,
                            is_alias_for=None,
                            reason_to_blacklist='')
        if exact_tag:
            result.append(exact_tag)
        elif with_aliases:
            tag_alias = exact_tag = Tag.get(iname=iname,
                                            reason_to_blacklist='')
            if tag_alias:
                exact_tag = tag_alias.is_alias_for
                assert exact_tag
                result.append(exact_tag)

        # Ищем остальные теги по префиксу
        if len(result) < limit:
            tags = set(
                Tag.select(lambda x: x.iname.startswith(iname) and not x.
                           is_alias and not x.is_blacklisted).order_by(
                               Tag.published_stories_count.desc())
                [:limit + len(result)])
            if with_aliases:
                # Подключаем синонимы, если разрешили
                # (отдельным запросом, чтоб были по порядку ниже несинонимов)
                tags = tags | set(
                    Tag.select(lambda x: x.iname.startswith(iname) and x.
                               is_alias and not x.is_blacklisted))
            tags = sorted(tags,
                          key=lambda x: x.published_stories_count,
                          reverse=True)
            result.extend([x for x in tags if x not in result])

        return result[:limit]
Ejemplo n.º 7
0
    def get_all_tags(self, only_main=False, sort=False):
        from mini_fiction.models import Tag

        q = Tag.select()
        q = q.filter(lambda x: not x.is_blacklisted and not x.is_alias)
        if only_main:
            q = q.filter(lambda x: x.is_main_tag)

        q = q.prefetch(Tag.category)
        tags = list(q)
        if sort:
            tags.sort(key=lambda x: (x.category.id if x.category else 2 ** 31, x.iname))
        return tags
Ejemplo n.º 8
0
    def get_all_tags(self, only_main=False, sort=False):
        from mini_fiction.models import Tag

        q = Tag.select()
        q = q.filter(lambda x: not x.is_blacklisted and not x.is_alias)
        if only_main:
            q = q.filter(lambda x: x.is_main_tag)

        q = q.prefetch(Tag.category)
        tags = list(q)
        if sort:
            tags.sort(key=lambda x: (x.category.id
                                     if x.category else 2**31, x.iname))
        return tags
Ejemplo n.º 9
0
    def get_aliases_for(self, tags, hidden=False):
        from mini_fiction.models import Tag

        for x in tags:
            if x.is_alias or x.is_blacklisted:
                raise ValueError('Only valid canonical tags are allowed for get_aliases_for')

        result = {x.id: [] for x in tags}
        q = Tag.select(lambda x: x.is_alias_for in tags)
        if not hidden:
            q = q.filter(lambda x: not x.is_hidden_alias)
        for ts in q:
            result[ts.is_alias_for.id].append(ts)
        return result
Ejemplo n.º 10
0
    def search_by_prefix(self, name, with_aliases=True, limit=20):
        from mini_fiction.models import Tag

        iname = normalize_tag(name)
        if not iname or limit < 1:
            return []
        if limit > 100:
            limit = 100

        result = []

        # Ищем точное совпадение
        exact_tag = Tag.get(iname=iname, is_alias_for=None, reason_to_blacklist='')
        if exact_tag:
            result.append(exact_tag)
        elif with_aliases:
            tag_alias = exact_tag = Tag.get(iname=iname, reason_to_blacklist='')
            if tag_alias:
                exact_tag = tag_alias.is_alias_for
                assert exact_tag
                result.append(exact_tag)

        # Ищем остальные теги по префиксу
        if len(result) < limit:
            tags = set(Tag.select(
                lambda x: x.iname.startswith(iname) and not x.is_alias and not x.is_blacklisted
            ).order_by(Tag.published_stories_count.desc())[:limit + len(result)])
            if with_aliases:
                # Подключаем синонимы, если разрешили
                # (отдельным запросом, чтоб были по порядку ниже несинонимов)
                tags = tags | set(Tag.select(
                    lambda x: x.iname.startswith(iname) and x.is_alias and not x.is_blacklisted
                ))
            tags = sorted(tags, key=lambda x: x.published_stories_count, reverse=True)
            result.extend([x for x in tags if x not in result])

        return result[:limit]
Ejemplo n.º 11
0
def check_tag_stories_count(verbosity=0, dry_run=False):
    last_id = None
    all_count = 0
    changed_count = 0

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

            for tag in tags:
                all_count += 1
                changed = False

                data = {
                    'stories_count':
                    StoryTag.select(lambda x: x.tag == tag).count(),
                    'published_stories_count':
                    StoryTag.select(
                        lambda x: x.tag == tag and x.story.published).count(),
                }

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

                if changed:
                    changed_count += 1

    if verbosity >= 1:
        print('{} tags available, {} tags changed'.format(
            all_count, changed_count),
              file=sys.stderr)
Ejemplo n.º 12
0
    def get_aliases_for(self, tags, hidden=False):
        from mini_fiction.models import Tag

        for x in tags:
            if x.is_alias or x.is_blacklisted:
                raise ValueError(
                    'Only valid canonical tags are allowed for get_aliases_for'
                )

        result = {x.id: [] for x in tags}
        q = Tag.select(lambda x: x.is_alias_for in tags)
        if not hidden:
            q = q.filter(lambda x: not x.is_hidden_alias)
        for ts in q:
            result[ts.is_alias_for.id].append(ts)
        return result
Ejemplo n.º 13
0
    def create(self, user, data):
        from mini_fiction.models import Tag, AdminLog

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

        data = Validator(TAG).validated(data)

        errors = {}

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

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

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

        if errors:
            raise ValidationError(errors)

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

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

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

        return tag
Ejemplo n.º 14
0
def tag_index(tag_name, page):
    iname = normalize_tag(tag_name)
    if not iname:
        abort(404)
    tag = Tag.get(iname=iname)
    if tag and tag.is_alias_for is not None:
        if tag.is_alias_for.is_alias_for:
            raise RuntimeError(
                'Tag alias {} refers to another alias {}!'.format(
                    tag.id, tag.is_alias_for.id))
        tag = tag.is_alias_for
    if not tag or tag.is_blacklisted:
        abort(404)
    if tag.iname != tag_name:
        return redirect(
            url_for('tags.tag_index', tag_name=tag.iname, page=page))

    objects = Story.bl.select_by_tag(tag,
                                     user=current_user._get_current_object())
    objects = objects.prefetch(Story.characters, Story.contributors,
                               StoryContributor.user, Story.tags, StoryTag.tag,
                               Tag.category)
    objects = objects.order_by(Story.first_published_at.desc(),
                               Story.id.desc())

    page_obj = Paginator(
        page,
        objects.count(),
        per_page=current_app.config['STORIES_COUNT']['stream'])
    objects = page_obj.slice_or_404(objects)

    return render_template(
        'tags/tag_index.html',
        page_title=tag.name,
        tag=tag,
        aliases=[x.name for x in Tag.bl.get_aliases_for([tag])[tag.id]],
        category=tag.category,
        stories=objects,
        page_obj=page_obj,
        **cached_lists([x.id for x in objects]))
Ejemplo n.º 15
0
def check_tag_stories_count(verbosity=0, dry_run=False):
    last_id = None
    all_count = 0
    changed_count = 0

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

            for tag in tags:
                all_count += 1
                changed = False

                data = {
                    'stories_count': StoryTag.select(lambda x: x.tag == tag).count(),
                    'published_stories_count': StoryTag.select(lambda x: x.tag == tag and x.story.published).count(),
                }

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

                if changed:
                    changed_count += 1

    if verbosity >= 1:
        print('{} tags available, {} tags changed'.format(all_count, changed_count), file=sys.stderr)
Ejemplo n.º 16
0
    def get_tags_with_categories(self, sort='name'):
        from mini_fiction.models import Tag

        categories_dict = {}

        tags = list(
            Tag.select(
                lambda x: not x.is_blacklisted and not x.is_alias).prefetch(
                    Tag.category))
        if sort == 'stories':
            tags.sort(key=lambda tag: tag.published_stories_count,
                      reverse=True)
        elif sort == 'date':
            tags.sort(key=lambda tag: tag.created_at, reverse=True)
        elif sort == 'name':
            tags.sort(key=lambda tag: tag.iname)
        else:
            raise ValueError('Invalid tags sorting: {!r}'.format(sort))

        categories_dict = {}
        others = {'category': None, 'tags': []}
        for tag in tags:
            if not tag.category:
                others['tags'].append(tag)
                continue
            if tag.category.id not in categories_dict:
                categories_dict[tag.category.id] = {
                    'category': tag.category,
                    'tags': []
                }
            categories_dict[tag.category.id]['tags'].append(tag)

        result = sorted(categories_dict.values(),
                        key=lambda x: x['category'].id)
        result.append(others)
        return result
Ejemplo n.º 17
0
    def update(self, user, data):
        from mini_fiction.models import Tag, AdminLog

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

        tag = self.model

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

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

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

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

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

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

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

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

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

        if 'reason_to_blacklist' in data:
            self.set_blacklist(user, data['reason_to_blacklist'])
        if not tag.is_blacklisted and ('is_alias_for' in data or 'is_hidden_alias' in data):
            self.make_alias_for(user, canonical_tag, data.get('is_hidden_alias', tag.is_hidden_alias))
Ejemplo n.º 18
0
    def get_tags_objects(self,
                         tags,
                         create=False,
                         user=None,
                         resolve_aliases=True,
                         resolve_blacklisted=True,
                         create_if_errors=False):
        """Принимает список объектов Tag или строк с названиями тегов
        (можно вперемешку) и возвращает словарь со следующими ключами:

        - success — True, если всё хорошо (в списке tags гарантированно
          отсутствуют None);

        - tags — список из объектов Tag, которые были успешно найдены
          (соответствует порядку исходному списку tags, вместо ненайденных
          тегов стоит None);

        - aliases — список тегов, оказавшихся алиасами и заменённых
          на каноничные теги (пуст при resolve_aliases=False);

        - blacklisted — список заблокированных тегов, не попавших в tags
          (пуст при resolve_blacklisted=False);

        - invalid — список строк, которые не являются синтаксически
          корректными тегами (например, пустая строка), и причин
          некорректности (кортежи из двух элементов);

        - created — список свежесозданных тегов, если они создавались
          (при create=True);

        - nonexisting — список из строк, для которых тегов не нашлось
          (при create=False).

        Если скормить несколько одинаковых тегов, то в списке tags могут
        появиться дубликаты.
        """

        # FIXME: тут костыль с е/ё. Надо бы как-то заменить его на адекватное
        # юникодное сравнение для соответствия utf8mb4_general_ci

        from mini_fiction.validation.utils import safe_string_coerce
        from mini_fiction.models import Tag

        # Достаём список строк для поиска тегов
        tags_search = [x for x in tags if not isinstance(x, Tag)]
        tags_db = {
            x.iname.replace('ё', 'е'): x
            for x in tags if isinstance(x, Tag)
        }

        # Ищем недостающие теги в базе
        inames = [normalize_tag(x) for x in tags_search]
        inames = [x for x in inames if x]
        if inames:
            tags_db.update({
                x.iname.replace('ё', 'е'): x
                for x in Tag.select(lambda t: t.iname in inames).prefetch(
                    Tag.is_alias_for)
            })

        result = {
            'success': True,
            'tags': [None] * len(tags),
            'aliases': [],
            'blacklisted': [],
            'invalid': [],
            'created': [],
            'nonexisting': [],
        }

        create_tags = []  # [(index, name, iname), ...]

        # Анализируем каждый запрошенный тег
        for i, x in enumerate(tags):
            if isinstance(x, Tag):
                name = x.name
                iname = x.iname
                tag = x
            else:
                name = safe_string_coerce(x.strip())
                iname = normalize_tag(name)
                tag = None
                if iname:
                    tag = tags_db.get(iname.replace('ё', 'е'))
                    assert iname == normalize_tag(x)

            if tag:
                # Если тег существует, проверяем, что его можно использовать
                if resolve_aliases and tag.is_alias_for:
                    if tag.is_alias_for.is_alias_for:
                        raise RuntimeError(
                            'Tag alias {} refers to another alias {}!'.format(
                                tag.id, tag.is_alias_for.id))
                    result['aliases'].append(tag)
                    tag = tag.is_alias_for
                if resolve_blacklisted and tag.is_blacklisted:
                    result['blacklisted'].append(tag)
                    result['success'] = False
                    tag = None

            elif create:
                # Если не существует — создаём
                if not user or not user.is_authenticated:
                    raise ValueError('Not authenticated')
                reason = self.validate_tag_name(name)
                if reason is not None:
                    result['invalid'].append((x, reason))
                    result['success'] = False
                else:
                    create_tags.append(
                        (i, name, iname))  # Отложенное создание тегов

            else:
                result['nonexisting'].append(x)
                result['success'] = False

            if tag:
                result['tags'][i] = tag

        # Если нужно создать теги, то создаём их только при отсутствии других
        # ошибок, чтобы зазря не мусорить в базу данных (проверка отключается
        # опцией create_if_errors=True)
        if create_tags and (create_if_errors or result['success']):
            for i, name, iname in create_tags:
                if iname.replace('ё', 'е') in tags_db:
                    # На случай, если пользователь пропихнул дублирующиеся теги
                    result['tags'][i] = tags_db[iname.replace('ё', 'е')]
                    continue
                tag = Tag(name=name, iname=iname, created_by=user)
                tag.flush()  # получаем id у базы данных
                tags_db[tag.iname.replace(
                    'ё', 'е'
                )] = tag  # На случай, если у следующего тега в цикле совпадёт iname
                result['created'].append(tag)
                result['tags'][i] = tag

            # see views/tags.py
            current_app.cache.delete('tags_autocomplete_default')

        return result
Ejemplo n.º 19
0
    def update(self, user, data):
        from mini_fiction.models import Tag, AdminLog

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

        tag = self.model

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

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

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

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

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

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

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

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

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

        if 'reason_to_blacklist' in data:
            self.set_blacklist(user, data['reason_to_blacklist'])
        if not tag.is_blacklisted and ('is_alias_for' in data
                                       or 'is_hidden_alias' in data):
            self.make_alias_for(
                user, canonical_tag,
                data.get('is_hidden_alias', tag.is_hidden_alias))
Ejemplo n.º 20
0
    def get_tags_objects(
        self, tags, create=False, user=None, resolve_aliases=True,
        resolve_blacklisted=True, create_if_errors=False
    ):
        """Принимает список объектов Tag или строк с названиями тегов
        (можно вперемешку) и возвращает словарь со следующими ключами:

        - success — True, если всё хорошо (в списке tags гарантированно
          отсутствуют None);

        - tags — список из объектов Tag, которые были успешно найдены
          (соответствует порядку исходному списку tags, вместо ненайденных
          тегов стоит None);

        - aliases — список тегов, оказавшихся алиасами и заменённых
          на каноничные теги (пуст при resolve_aliases=False);

        - blacklisted — список заблокированных тегов, не попавших в tags
          (пуст при resolve_blacklisted=False);

        - invalid — список строк, которые не являются синтаксически
          корректными тегами (например, пустая строка), и причин
          некорректности (кортежи из двух элементов);

        - created — список свежесозданных тегов, если они создавались
          (при create=True);

        - nonexisting — список из строк, для которых тегов не нашлось
          (при create=False).

        Если скормить несколько одинаковых тегов, то в списке tags могут
        появиться дубликаты.
        """

        from mini_fiction.validation.utils import safe_string_coerce
        from mini_fiction.models import Tag

        # Достаём список строк для поиска тегов
        tags_search = [x for x in tags if not isinstance(x, Tag)]
        tags_db = {x.iname: x for x in tags if isinstance(x, Tag)}

        # Ищем недостающие теги в базе
        inames = [normalize_tag(x) for x in tags_search]
        inames = [x for x in inames if x]
        if inames:
            tags_db.update({x.iname: x for x in Tag.select(lambda t: t.iname in inames).prefetch(Tag.is_alias_for)})

        result = {
            'success': True,
            'tags': [None] * len(tags),
            'aliases': [],
            'blacklisted': [],
            'invalid': [],
            'created': [],
            'nonexisting': [],
        }

        create_tags = []  # [(index, name, iname), ...]

        # Анализируем каждый запрошенный тег
        for i, x in enumerate(tags):
            if isinstance(x, Tag):
                name = x.name
                iname = x.iname
                tag = x
            else:
                name = safe_string_coerce(x.strip())
                iname = normalize_tag(name)
                tag = tags_db.get(iname)
                assert iname == normalize_tag(x)

            if tag:
                # Если тег существует, проверяем, что его можно использовать
                if resolve_aliases and tag.is_alias_for:
                    if tag.is_alias_for.is_alias_for:
                        raise RuntimeError('Tag alias {} refers to another alias {}!'.format(tag.id, tag.is_alias_for.id))
                    result['aliases'].append(tag)
                    tag = tag.is_alias_for
                if resolve_blacklisted and tag.is_blacklisted:
                    result['blacklisted'].append(tag)
                    result['success'] = False
                    tag = None

            elif create:
                # Если не существует — создаём
                if not user or not user.is_authenticated:
                    raise ValueError('Not authenticated')
                reason = self.validate_tag_name(name)
                if reason is not None:
                    result['invalid'].append((x, reason))
                    result['success'] = False
                else:
                    create_tags.append((i, name, iname))  # Отложенное создание тегов

            else:
                result['nonexisting'].append(x)
                result['success'] = False

            if tag:
                result['tags'][i] = tag

        # Если нужно создать теги, то создаём их только при отсутствии других
        # ошибок, чтобы зазря не мусорить в базу данных (проверка отключается
        # опцией create_if_errors=True)
        if create_tags and (create_if_errors or result['success']):
            for i, name, iname in create_tags:
                if iname in tags_db:
                    # На случай, если пользователь пропихнул дублирующиеся теги
                    result['tags'][i] = tags_db[iname]
                    continue
                tag = Tag(name=name, iname=iname, created_by=user)
                tag.flush()  # получаем id у базы данных
                tags_db[tag.iname] = tag  # На случай, если у следующего тега в цикле совпадёт iname
                result['created'].append(tag)
                result['tags'][i] = tag

            # see views/tags.py
            current_app.cache.delete('tags_autocomplete_default')

        return result
Ejemplo n.º 21
0
def index(page):
    objects = Tag.select().order_by(Tag.iname)

    args = {
        'page': page,
        'sorting': request.args.get('sorting') or 'iname',
    }

    if request.args.get('name'):
        iname = normalize_tag(request.args['name'])
        args['name'] = iname
        objects = objects.filter(lambda x: iname in x.iname)

    if request.args.get('category'):
        if request.args['category'] == '0':
            args['category'] = '0'
            objects = objects.filter(lambda x: x.category is None)
        elif request.args['category'].isdigit():
            args['category'] = request.args['category']
            cat_id = int(request.args['category'])
            objects = objects.filter(lambda x: x.category.id == cat_id)

    if request.args.get('is_blacklisted') == '0':
        args['is_blacklisted'] = '0'
        objects = objects.filter(lambda x: not x.is_blacklisted)
    elif request.args.get('is_blacklisted') == '1':
        args['is_blacklisted'] = '1'
        objects = objects.filter(lambda x: x.is_blacklisted)

    if request.args.get('is_alias') == '0':
        args['is_alias'] = '0'
        objects = objects.filter(lambda x: not x.is_alias)
    elif request.args.get('is_alias') == '1':
        args['is_alias'] = '1'
        objects = objects.filter(lambda x: x.is_alias)

    if request.args.get('is_main_tag') == '0':
        args['is_main_tag'] = '0'
        objects = objects.filter(lambda x: not x.is_main_tag)
    elif request.args.get('is_main_tag') == '1':
        args['is_main_tag'] = '1'
        objects = objects.filter(lambda x: x.is_main_tag)

    objects = objects.prefetch(Tag.is_alias_for, Tag.created_by, Tag.category)

    objects = admin_sort(args['sorting'], objects, {
        'iname': Tag.iname,
        'created_at': (Tag.created_at, Tag.id),
        'stories_count': (Tag.stories_count, Tag.id),
    }, default='iname')

    page_obj = Paginator(page, objects.count(), per_page=100, endpoint=request.endpoint, view_args=args)

    return render_template(
        'admin/tags/index.html',
        tags=page_obj.slice_or_404(objects),
        page_obj=page_obj,
        page_title=gettext('Tags'),
        endpoint=request.endpoint,
        args=args,
        tag_categories=list(TagCategory.select().order_by(TagCategory.id)),
    )
Ejemplo n.º 22
0
def index(page):
    objects = Tag.select().order_by(Tag.iname)

    args = {
        'page': page,
        'sorting': request.args.get('sorting') or 'iname',
    }

    if request.args.get('name'):
        iname = normalize_tag(request.args['name'])
        args['name'] = iname
        objects = objects.filter(lambda x: iname in x.iname)

    if request.args.get('category'):
        if request.args['category'] == '0':
            args['category'] = '0'
            objects = objects.filter(lambda x: x.category is None)
        elif request.args['category'].isdigit():
            args['category'] = request.args['category']
            cat_id = int(request.args['category'])
            objects = objects.filter(lambda x: x.category.id == cat_id)

    if request.args.get('is_blacklisted') == '0':
        args['is_blacklisted'] = '0'
        objects = objects.filter(lambda x: not x.is_blacklisted)
    elif request.args.get('is_blacklisted') == '1':
        args['is_blacklisted'] = '1'
        objects = objects.filter(lambda x: x.is_blacklisted)

    if request.args.get('is_alias') == '0':
        args['is_alias'] = '0'
        objects = objects.filter(lambda x: not x.is_alias)
    elif request.args.get('is_alias') == '1':
        args['is_alias'] = '1'
        objects = objects.filter(lambda x: x.is_alias)

    if request.args.get('is_main_tag') == '0':
        args['is_main_tag'] = '0'
        objects = objects.filter(lambda x: not x.is_main_tag)
    elif request.args.get('is_main_tag') == '1':
        args['is_main_tag'] = '1'
        objects = objects.filter(lambda x: x.is_main_tag)

    objects = objects.prefetch(Tag.is_alias_for, Tag.created_by, Tag.category)

    objects = admin_sort(args['sorting'],
                         objects, {
                             'iname': Tag.iname,
                             'created_at': (Tag.created_at, Tag.id),
                             'stories_count': (Tag.stories_count, Tag.id),
                         },
                         default='iname')

    page_obj = Paginator(page,
                         objects.count(),
                         per_page=100,
                         endpoint=request.endpoint,
                         view_args=args)

    return render_template(
        'admin/tags/index.html',
        tags=page_obj.slice_or_404(objects),
        page_obj=page_obj,
        page_title=gettext('Tags'),
        endpoint=request.endpoint,
        args=args,
        tag_categories=list(TagCategory.select().order_by(TagCategory.id)),
    )