コード例 #1
0
def sorts_right(objects):
    assert {y.expected_position
            for y in values(objects)
            } == set(range(len(objects))), "Borken test"
    sort_after(objects)
    assert [x.expected_position for x in values(objects)
            ] == list(range(len(objects))), keys(objects)
コード例 #2
0
def test_unbound_error():
    class MyBasket(Basket):
        orange = Fruit(taste='sour')

    expected = 'fruits of MyBasket is not bound, look in _declared_members[fruits] for the declared copy of this, or bind first'

    basket = MyBasket()
    assert repr(basket.fruits) == expected

    with pytest.raises(NotBoundYetException) as e:
        items(basket.fruits)

    with pytest.raises(NotBoundYetException) as e2:
        str(basket.fruits)

    with pytest.raises(NotBoundYetException) as e3:
        keys(basket.fruits)

    with pytest.raises(NotBoundYetException) as e4:
        values(basket.fruits)

    with pytest.raises(NotBoundYetException) as e5:
        for _ in basket.fruits:
            pass  # pragma: no cover as it is supposed to raise on iter

    assert str(e.value) == str(e2.value) == str(e3.value) == str(
        e4.value) == str(e5.value)
    assert str(e.value) == expected
コード例 #3
0
    def render_fields(self):
        r = []
        for field in values(self.fields):
            r.append(field.__html__())

        # We need to preserve all other GET parameters, so we can e.g. filter in two forms on the same page, and keep sorting after filtering
        own_field_paths = {f.iommi_path for f in values(self.fields)}
        for k, v in items(self.get_request().GET):
            if k not in own_field_paths and not k.startswith('-'):
                r.append(format_html('<input type="hidden" name="{}" value="{}" />', k, v))

        return format_html('{}\n' * len(r), *r)
コード例 #4
0
ファイル: query.py プロジェクト: BrentGuttmann/iommi
    def _freetext_to_q(self, token):
        if all(not v.freetext for v in values(self.filters)):
            raise QueryException('There are no freetext filters available')
        assert len(token) == 1
        token = token[0].strip('"')

        return reduce(operator.or_, [
            Q(
                **{
                    filter.attr + '__' + filter.query_operator_to_q_operator(':'):
                    token
                }) for filter in values(self.filters) if filter.freetext
        ])
コード例 #5
0
ファイル: menu.py プロジェクト: TriOptima/iommi
    def on_bind(self):
        bind_members(self, name='sub_menu')

        if self.sort:
            self.sub_menu = Struct(
                {item._name: item for item in sorted(values(self.sub_menu), key=lambda x: x.display_name)}
            )
コード例 #6
0
ファイル: query.py プロジェクト: forksbot/iommi
    def on_bind(self) -> None:
        bind_members(self, name='filters')
        self.advanced_simple_toggle = self.advanced_simple_toggle.bind(parent=self)

        request = self.get_request()
        self.query_advanced_value = request_data(request).get(self.get_advanced_query_param(), '') if request else ''

        # TODO: should it be possible to have freetext as a callable? this code just treats callables as truthy
        if any(f.freetext for f in values(declared_members(self)['filters'])):
            declared_members(self.form).fields[FREETEXT_SEARCH_NAME].include = True

        declared_fields = declared_members(self.form)['fields']
        for name, filter in items(self.filters):
            assert filter.attr or not getattr(filter.value_to_q, 'iommi_needs_attr', False), f"{name} cannot be a part of a query, it has no attr or value_to_q so we don't know what to search for"
            if name in declared_fields:
                field = setdefaults_path(
                    Namespace(),
                    _name=name,
                    attr=name if filter.attr is MISSING else filter.attr,
                    model_field=filter.model_field,
                )
                declared_fields[name] = declared_fields[name].reinvoke(field)
        set_declared_member(self.form, 'fields', declared_fields)
        for name, field in items(declared_fields):
            if name == FREETEXT_SEARCH_NAME:
                continue
            if name not in self.filters:
                field.include = False

        bind_members(self, name='endpoints')

        self.form = self.form.bind(parent=self)
        self._bound_members.form = self.form
コード例 #7
0
 def render_text_or_children(self, context):
     request = self.get_request()
     return format_html(
         '{}' * len(self.children), *[
             as_html(part=x, context=context, request=request)
             for x in values(self.children)
         ])
コード例 #8
0
def create_or_edit_object__post_handler(*, form, is_create, **_):
    if is_create:
        assert form.instance is None
        form.instance = form.model()
        for field in values(form.fields):  # two phase save for creation in django, have to save main object before related stuff
            if not field.extra.get('django_related_field', False):
                form.apply_field(field=field, instance=form.instance)

    try:
        form.instance.validate_unique()
    except ValidationError as e:
        form.errors.update(set(e.messages))
        form._valid = False  # pragma: no mutate. False here is faster, but setting it to None is also fine, it just means _valid will be calculated the next time form.is_valid() is called

    if not form.is_valid():
        return

    if is_create:  # two phase save for creation in django...
        form.instance.save()

    form.apply(form.instance)

    if not is_create:
        try:
            form.instance.validate_unique()
        except ValidationError as e:
            form.errors.update(set(e.messages))
            form._valid = False  # pragma: no mutate. False here is faster, but setting it to None is also fine, it just means _valid will be calculated the next time form.is_valid() is called

    if form.is_valid():
        form.instance.save()

        form.extra.on_save(form=form, instance=form.instance)

        return create_or_edit_object_redirect(is_create, form.extra.redirect_to, form.get_request(), form.extra.redirect, form)
コード例 #9
0
 def apply(self, instance):
     """
     Write the new values specified in the form into the instance specified.
     """
     assert self.is_valid()
     for field in values(self.fields):
         self.apply_field(instance=instance, field=field)
     return instance
コード例 #10
0
 def get_errors(self):
     self.is_valid()
     r = {}
     if self.errors:
         r['global'] = self.errors
     field_errors = {x._name: x.errors for x in values(self.fields) if x.errors}
     if field_errors:
         r['fields'] = field_errors
     return r
コード例 #11
0
ファイル: menu.py プロジェクト: TriOptima/iommi
        def _validate(item):
            for sub_item in values(item.sub_menu):
                if sub_item.url is None or '://' in sub_item.url or sub_item.url.startswith('#'):
                    continue

                _validate(sub_item)

                path = urlparse(sub_item.url).path
                paths[path].append(sub_item.iommi_path)
コード例 #12
0
ファイル: action.py プロジェクト: viktor2097/iommi
def group_actions(actions: Dict[str, Action]):
    actions_with_group = (action for action in values(actions)
                          if action.group is not None)

    grouped_actions: List[Tuple[str, str, List[Action]]] = [
        (group_name, slugify(group_name), list(actions_in_group))
        for group_name, actions_in_group in groupby(actions_with_group,
                                                    key=lambda l: l.group)
    ]

    for _, _, actions_in_group in grouped_actions:
        for action in actions_in_group:
            action.attrs.role = 'menuitem'
            action.attrs['class']['dropdown-item'] = True

    actions_without_group = [
        action for action in values(actions) if action.group is None
    ]
    return actions_without_group, grouped_actions
コード例 #13
0
 def is_valid(self):
     if self._valid is None:
         self.validate()
         for field in values(self.fields):
             if field.errors:
                 self._valid = False
                 break
         else:
             self._valid = not self.errors
     return self._valid
コード例 #14
0
    def get_query_string(self):
        """
        Based on the data in the request, return the equivalent query string that you can use with parse_query_string() to create a query set.
        """
        form = self.form
        request = self.get_request()

        if request is None:
            return ''

        if request_data(request).get(self.get_advanced_query_param(), '').strip():
            return request_data(request).get(self.get_advanced_query_param())
        elif form.is_valid():

            def expr(field, is_list, value):
                if is_list:
                    return '(' + ' OR '.join([expr(field, is_list=False, value=x) for x in field.value]) + ')'
                return build_query_expression(filter=self.filters[field._name], value=value)

            result = [
                expr(field, field.is_list, field.value)
                for field in values(form.fields)
                if field._name != FREETEXT_SEARCH_NAME
                and field._name in self.filters
                and field.value not in (None, '', [])
            ]

            if FREETEXT_SEARCH_NAME in form.fields:
                freetext = form.fields[FREETEXT_SEARCH_NAME].value
                if freetext:
                    result.append(
                        '(%s)'
                        % ' or '.join(
                            [
                                f'{filter.query_name}:{to_string_surrounded_by_quote(freetext)}'
                                for filter in values(self.filters)
                                if filter.freetext
                            ]
                        )
                    )
            return ' and '.join(result)
        else:
            return ''
コード例 #15
0
def test_bulk_edit_for_non_unique(settings):
    settings.ROOT_URLCONF = __name__
    request = staff_req('get')
    p = Admin.list(
        request=request,
        app_name='tests',
        model_name='adminunique',
        parts__list_tests_adminunique__columns__foo__bulk__include=True,
    )
    p = p.bind(request=request)
    assert [x._name for x in values(p.parts.list_tests_adminunique.columns) if x.bulk.include] == ['foo']
コード例 #16
0
ファイル: query.py プロジェクト: Stevenans985900/iommi
    def on_bind(self) -> None:
        bind_members(self, name='filters')

        request = self.get_request()
        self.query_advanced_value = request_data(request).get(
            self.get_advanced_query_param(), '') if request else ''

        # TODO: should it be possible to have freetext as a callable? this code just treats callables as truthy
        if any(f.freetext for f in values(declared_members(self)['filters'])):
            set_and_remember_for_reinvoke(declared_members(
                self.form).fields[FREETEXT_SEARCH_NAME],
                                          include=True)

        declared_fields = declared_members(self.form)['fields']
        for name, filter in items(self.filters):
            is_valid, message = filter.is_valid_filter(name=name,
                                                       filter=filter)
            assert is_valid, message
            if name in declared_fields:
                field = setdefaults_path(
                    Namespace(),
                    _name=name,
                    attr=name if filter.attr is MISSING else filter.attr,
                    model_field=filter.model_field,
                    help__include=False,
                )
                declared_fields[name] = reinvoke(declared_fields[name], field)
        set_declared_member(self.form, 'fields', declared_fields)

        # Remove fields from the form that correspond to non-included filters
        declared_filters = declared_members(self)['filters']
        for name, field in items(declared_fields):
            if name == FREETEXT_SEARCH_NAME:
                continue
            # We need to check if it's in declared_filters first, otherwise we remove any injected fields
            if name in declared_filters and name not in self.filters:
                set_and_remember_for_reinvoke(field, include=False)

        bind_members(self, name='endpoints')

        self.form = self.form.bind(parent=self)
        self._bound_members.form = self.form

        self.advanced = self._bound_members.advanced = self.advanced.bind(
            parent=self)

        self.form_container = self.form_container.bind(parent=self)

        self.filter_name_by_query_name = {
            x.query_name: name
            for name, x in items(self.filters)
        }
コード例 #17
0
ファイル: debug.py プロジェクト: tltx/iommi
    def rows(node, name='', path=None):
        if path is None:
            path = []
        is_struct = type(node) is Struct
        is_bound = getattr(node, '_is_bound', False)

        try:
            p = node.iommi_path if is_bound else None
        except PathNotFoundException:
            p = None

        type_name = type(node).__name__ if not is_struct else None
        base_type_name = type_name
        if isinstance(node, Members) and node._declared_members:
            if name == 'parts':
                member_type = 'Part'
            else:
                member_type = type(list(values(declared_members(node)))[0]).__name__
            type_name = f'Members[{member_type}]'

        children = []
        if isinstance(node, dict):
            children = list(node.items())
        elif isinstance(node, Traversable):
            children = [
                (
                    k,
                    node.iommi_bound_members().get(k, v)
                )
                for k, v in items(declared_members(node))
            ]

        if (isinstance(node, Members) or isinstance(node, dict)) and not children:
            return

        yield Struct(
            name=name,
            obj=node,
            type=type_name,
            base_type=base_type_name,
            path=p,
            dunder_path='__'.join(path),
            included=is_bound
        )

        for k, v in children:
            yield from rows(v, name=k, path=path + [k])
コード例 #18
0
        def _set_active(item):
            nonlocal current_parts_matching
            nonlocal current
            for sub_item in values(item.sub_menu):
                _set_active(sub_item)

                if sub_item.url is None or '://' in sub_item.url:
                    continue

                parsed_url = urlparse(sub_item.url).path

                if current_path.startswith(parsed_url):
                    parts = PurePosixPath(unquote(parsed_url)).parts
                    matching_parts = 0
                    for item in range(min(len(parts), len(path_parts))):
                        if parts[item] is path_parts[item]:
                            matching_parts += 1

                    if matching_parts > current_parts_matching:
                        current = sub_item
                        current_parts_matching = matching_parts
コード例 #19
0
 def is_target(self):
     return any(action.is_target() for action in values(self.actions))
コード例 #20
0
ファイル: admin.py プロジェクト: viktor2097/iommi
    Header,
    html,
    Menu,
    MenuItem,
    Page,
    Table,
)
from iommi.base import (
    items,
    values,
)
from iommi.reinvokable import reinvokable

app_verbose_name_by_label = {
    config.label: config.verbose_name
    for config in values(django_apps.app_configs)
}

joined_app_name_and_model = {
    f'{app_name}_{model_name}'
    for app_name, models in items(django_apps.all_models)
    for model_name, model in items(models)
}


def require_login(view):
    @wraps(view)
    def wrapper(cls, request, *args, **kwargs):
        if not getattr(request, 'user', None) or not request.user.is_authenticated:
            return HttpResponseRedirect(f'{reverse(Auth.login)}?{urlencode(dict(next=request.path))}')
コード例 #21
0
 def __repr__(self):
     r = f'{self._name}'
     if self.sub_menu:
         for items in values(self.sub_menu):
             r += ''.join([f'\n    {x}' for x in repr(items).split('\n')])
     return r
コード例 #22
0
ファイル: fragment.py プロジェクト: forksbot/iommi
 def render_text_or_children(self, context):
     assert not isinstance(context, RequestContext)
     return format_html(
         '{}' * len(self.children),
         *[as_html(part=x, context=context) for x in values(self.children)])
コード例 #23
0
 def validate(self):
     for field in values(self.fields):
         field.post_validation(**field.iommi_evaluate_parameters())
     self.post_validation(**self.iommi_evaluate_parameters())
     return self
コード例 #24
0
class Page(Part):
    """
    A page is used to compose iommi parts into a bigger whole.

    See the `howto <https://docs.iommi.rocks/en/latest/howto.html#parts-pages>`_ for example usages.
    """

    title: str = EvaluatedRefinable()
    member_class: Type[Fragment] = Refinable()
    context = Refinable(
    )  # context is evaluated, but in a special way so gets no EvaluatedRefinable type

    class Meta:
        member_class = Fragment

    @reinvokable
    @dispatch(
        parts=EMPTY,
        context=EMPTY,
    )
    def __init__(self,
                 *,
                 _parts_dict: Dict[str, PartType] = None,
                 parts: dict,
                 **kwargs):
        super(Page, self).__init__(**kwargs)

        self.parts = {
        }  # This is just so that the repr can survive if it gets triggered before parts is set properly

        # First we have to up sample parts that aren't Part into Fragment
        def as_fragment_if_needed(k, v):
            if v is None:
                return None
            if not isinstance(v, (dict, Traversable)):
                return Fragment(children__text=v, _name=k)
            else:
                return v

        _parts_dict = {
            k: as_fragment_if_needed(k, v)
            for k, v in items(_parts_dict)
        }
        parts = Namespace(
            {k: as_fragment_if_needed(k, v)
             for k, v in items(parts)})

        collect_members(self,
                        name='parts',
                        items=parts,
                        items_dict=_parts_dict,
                        cls=self.get_meta().member_class)

    def on_bind(self) -> None:
        bind_members(self, name='parts')
        if self.context and self.iommi_parent() != None:
            assert False, 'The context property is only valid on the root page'

    def own_evaluate_parameters(self):
        return dict(page=self)

    @dispatch(render=lambda rendered: format_html('{}' * len(rendered),
                                                  *values(rendered)))
    def __html__(self, *, render=None):
        self.context = evaluate_strict_container(
            self.context or {}, **self.iommi_evaluate_parameters())
        rendered = {
            name: as_html(request=self.get_request(),
                          part=part,
                          context=self.iommi_evaluate_parameters())
            for name, part in items(self.parts)
        }

        return render(rendered)

    def as_view(self):
        return build_as_view_wrapper(self)
コード例 #25
0
ファイル: admin__tests.py プロジェクト: forksbot/iommi
def test_bulk_edit_for_non_unique():
    request = req('get')
    request.user = Struct(is_staff=True)
    p = Admin.list(request=request, app_name='tests', model_name='adminunique')
    p = p.bind(request=request)
    assert [x._name for x in values(p.parts.list_tests_adminunique.columns) if x.bulk.include] == ['foo']