2

我想path在 Django 3 中创建自定义函数,支持将装饰器应用于 URL 模式。我在GitHub中 看到了这个并尝试将其更新到 Django 3。这些是我更新的代码:

from functools import partial

from django.core.exceptions import ImproperlyConfigured
from django.urls.resolvers import RegexPattern, RoutePattern

from django.utils.encoding import force_text
import six
from django.urls import ResolverMatch, Resolver404, get_callable, URLResolver, URLPattern


class DecorateURLResolver(URLResolver):
    def __init__(self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None, wrap=None):
        super(DecorateURLResolver, self).__init__(pattern, urlconf_name, default_kwargs, app_name, namespace)
        if isinstance(wrap, (tuple, list)):
            self.wrap = wrap
        elif wrap:
            self.wrap = [wrap]
        else:
            self.wrap = []

    def resolve(self, path):
        path = str(path)  # path may be a reverse_lazy object
        tried = []
        match = self.pattern.match(path)
        if match:
            new_path, args, kwargs = match
            for pattern in self.url_patterns:
                try:
                    sub_match = pattern.resolve(new_path)
                except Resolver404 as e:
                    sub_tried = e.args[0].get('tried')
                    if sub_tried is not None:
                        tried.extend([pattern] + t for t in sub_tried)
                    else:
                        tried.append([pattern])
                else:
                    if sub_match:
                        # Merge captured arguments in match with submatch
                        sub_match_dict = {**kwargs, **self.default_kwargs}
                        # Update the sub_match_dict with the kwargs from the sub_match.
                        sub_match_dict.update(sub_match.kwargs)
                        # If there are *any* named groups, ignore all non-named groups.
                        # Otherwise, pass all non-named arguments as positional arguments.
                        sub_match_args = sub_match.args
                        if not sub_match_dict:
                            sub_match_args = args + sub_match.args
                        current_route = '' if isinstance(pattern, URLPattern) else str(pattern.pattern)
                        return ResolverMatch(
                            sub_match.func,
                            sub_match_args,
                            sub_match_dict,
                            sub_match.url_name,
                            [self.app_name] + sub_match.app_names,
                            [self.namespace] + sub_match.namespaces,
                            self._join_route(current_route, sub_match.route),
                        )
                    tried.append([pattern])
            raise Resolver404({'tried': tried, 'path': new_path})
        raise Resolver404({'path': path})

    def _decorate(self, callback):
        for decorator in reversed(self.wrap):
            callback = decorator(callback)
        return callback


class DecorateURLPattern(URLPattern):
    def __init__(self, pattern, callback, default_args=None, name=None, wrap=None):
        super(DecorateURLPattern, self).__init__(pattern, callback, default_args, name)
        if isinstance(wrap, (tuple, list)):
            self.wrap = wrap
        elif wrap:
            self.wrap = [wrap]
        else:
            self.wrap = []

    @property
    def callback(self):
        if self._callback is not None:
            return self._decorate(self._callback)
        self._callback = get_callable(self._callback_str)
        return self._decorate(self._callback)

    def _decorate(self, callback):
        for decorator in reversed(self.wrap):
            callback = decorator(callback)
        return callback


def _custom_path(route, view, kwargs=None, name=None, Pattern=None, wrap=None):
    if isinstance(view, (list, tuple)):
        # For include(...) processing.
        pattern = Pattern(route, is_endpoint=False)
        urlconf_module, app_name, namespace = view
        return DecorateURLResolver(
            pattern,
            urlconf_module,
            kwargs,
            app_name=app_name,
            namespace=namespace,
            wrap=wrap,

        )
    elif callable(view):
        pattern = Pattern(route, name=name, is_endpoint=True)
        return DecorateURLPattern(pattern, view, kwargs, name, wrap)
    else:
        raise TypeError('view must be a callable or a list/tuple in the case of include().')


custom_path = partial(_custom_path, Pattern=RoutePattern)
re_custom_path = partial(_custom_path, Pattern=RegexPattern)

但它不起作用并引发此错误:

    self.callback = callback  # the view AttributeError: can't set attribute

请帮助我将该源代码更新为 Django 3。

4

1 回答 1

0

您的解决方案似乎是正确的,并且您几乎已经正确完成了移植工作。

在我看来,唯一缺少的是:


class DecorateURLResolver(URLResolver):
    ...
    def resolve(self, path):
        ...
        if match:
            new_path, args, kwargs = match
            for pattern in self.url_patterns:
                try:
                    sub_match = pattern.resolve(new_path)
                except Resolver404 as e:
                    sub_tried = e.args[0].get('tried')
                    if sub_tried is not None:
                        tried.extend([pattern] + t for t in sub_tried)
                    else:
                        tried.append([pattern])
                else:
                    if sub_match:
                        ...
                        return ResolverMatch(
                            self._decorate(sub_match.func),  # here you had missed decorating sub_match.func
                            sub_match_args,
                            sub_match_dict,
                            sub_match.url_name,
                            [self.app_name] + sub_match.app_names,
                            [self.namespace] + sub_match.namespaces,
                            self._join_route(current_route, sub_match.route),
                        )
                    tried.append([pattern])
            raise Resolver404({'tried': tried, 'path': new_path})
        raise Resolver404({'path': path})

于 2020-02-26T10:11:32.890 回答