【问题标题】:Patch request not patching - 403 returned - django rest framework补丁请求未打补丁 - 返回 403 - django rest 框架
【发布时间】:2022-01-10 05:12:03
【问题描述】:

我正在尝试使用补丁请求测试 API 端点以确保其正常工作。

我正在使用APILiveServerTestCase,但似乎无法获得修补该项目所需的权限。我创建了一个用户 (adminuser),他是超级管理员,可以访问所有内容和所有权限。

我的测试用例如下所示:

class FutureVehicleURLTest(APILiveServerTestCase):
    def setUp(self):
        # Setup users and some vehicle data we can query against
        management.call_command("create_users_and_vehicle_data", verbosity=0)
        self.user = UserFactory()
        self.admin_user = User.objects.get(username="adminuser")
        self.future_vehicle = f.FutureVehicleFactory(
            user=self.user,
            last_updated_by=self.user,
        )
        self.vehicle = f.VehicleFactory(
            user=self.user,
            created_by=self.user,
            modified_by=self.user,
        )
        self.url = reverse("FutureVehicles-list")
        self.full_url = self.live_server_url + self.url
        time = str(datetime.now())
        self.form_data = {
            "signature": "TT",
            "purchasing": True,
            "confirmed_at": time,
        }

我已经用多种不同的方式尝试了这个测试 - 都给出了相同的结果 (403)。

我已经在测试中设置了 python 调试器,并且我尝试在浏览器中实际转到 http://localhost:xxxxx/admin/ 并使用任何用户手动登录,但是当我单击登录时页面只是刷新并且我从未得到“登录” ' 以查看管理员。我不确定这是否是因为它不能在这样的调试器中完全工作。

我的测试看起来像这样(使用请求库):

    def test_patch_request_updates_object(self):
        data_dict = {
            "signature": "TT",
            "purchasing": "true",
            "confirmed_at": datetime.now().strftime("%m/%d/%Y, %H:%M:%S"),
        }
        url = self.full_url + str(self.future_vehicle.id) + "/"
        client = requests.Session()
        client.auth = HTTPBasicAuth(self.admin_user.username, "test")
        client.headers.update({"x-test": "true"})
        response = client.get(self.live_server_url + "/admin/")
        csrftoken = response.cookies["csrftoken"]
        # interact with the api
        response = client.patch(
            url,
            data=json.dumps(data_dict),
            cookies=response.cookies,
            headers={
                "X-Requested-With": "XMLHttpRequest",
                "X-CSRFTOKEN": csrftoken,
            },
        )
        # RESPONSE GIVES 403 PERMISSION DENIED
        fte_future_vehicle = FutureVehicle.objects.filter(
            id=self.future_vehicle.id
        ).first()
        # THIS ERRORS WITH '' not equal to 'TT'
        self.assertEqual(fte_future_vehicle.signature, "TT")

我使用APIRequestFactory 尝试了与the documentation 非常相似的方法 并强制认证:

    def test_patch_request_updates_object(self):
        data_dict = {
            "signature": "TT",
            "purchasing": "true",
            "confirmed_at": datetime.now().strftime("%m/%d/%Y, %H:%M:%S"),
        }
        url = self.full_url + str(self.future_vehicle.id) + "/"
        api_req_factory = APIRequestFactory()
        view = FutureVehicleViewSet.as_view({"patch": "partial_update"})
        api_request = api_req_factory.patch(
            url, json.dumps(data_dict), content_type="application/json"
        )
        force_authenticate(api_request, self.admin_user)
        response = view(api_request, pk=self.future_assignment.id)
        fte_future_assignment = FutureVehicle.objects.filter(
            id=self.future_assignment.id
        ).first()
        self.assertEqual(fte_future_assignment.signature, "TT")

如果我进入调试器查看响应,它总是403

viewset 本身很简单:

class FutureVehicleViewSet(ModelViewSet):
    serializer_class = FutureVehicleSerializer

    def get_queryset(self):
        queryset = FutureVehicle.exclude_denied.all()
        user_id = self.request.query_params.get("user_id", None)
        if user_id:
            queryset = queryset.filter(user_id=user_id)
        return queryset

序列化器是最基本的 - 它只是 FutureVehicle 模型和所有字段。

我只是不知道为什么我的用户不会登录 - 或者我在尝试修补时可能做错了什么?

总的来说,我对 Django Rest Framework 还是很陌生,所以任何指导都会有所帮助!

编辑添加 - 我的 DRF 设置如下所示:

REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
    "DATETIME_FORMAT": "%m/%d/%Y - %I:%M:%S %p",
    "DATE_INPUT_FORMATS": ["%Y-%m-%d"],
    "DEFAULT_AUTHENTICATION_CLASSES": [
        # Enabling this it will require Django Session (Including CSRF)
        "rest_framework.authentication.SessionAuthentication"
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        # Globally only allow IsAuthenticated users access to API Endpoints
        "rest_framework.permissions.IsAuthenticated"
    ],
}

我确定 adminuser 是我们希望登录的用户 - 如果我进入调试器并检查用户,他们作为用户存在。在创建过程中,创建的任何用户都将密码设置为“test”。

【问题讨论】:

  • 如果您无法登录管理员,该用户可能不存在或者是超级用户。也可能是您的 AUTHENTICATION_CLASSES 的问题查看设置并发布您在那里的内容
  • 嗨@Dap;我知道用户作为超级用户存在 - 当我在调试器中查询它们时,我可以在 User 列表中看到它们。另外——我三重检查了数据创建——它们被标记为is_superuser;我已经添加了 DRF 设置。
  • 您能否分享响应中的任何其他详细信息(例如发回的消息)。您还可以确认用户将is_active 设为真实吗?
  • 我可以确认用户is_active;当我使用request.Sessions 方法时,发回的消息似乎是"detail":"Authentication credentials were not provided",我认为它解释了一些事情 - 不知道我之前是怎么错过的......现在要弄清楚如何包含我猜想的那些(我已经过身份验证client.auth的用户)
  • 尝试将client.headers.updateclient.patch 调用中找到的标题一起使用

标签: python django django-rest-framework django-testing


【解决方案1】:

推荐解决方案

你写的测试也是测试Django框架逻辑(即:Django admin login)。我建议测试您自己的功能,这发生在 登录到 Django 管理员之后。 Django 的测试框架为登录管理员提供了帮助,client.login。这使您可以专注于测试自己的业务逻辑/不需要维护内部 django 身份验证业务逻辑测试,这可能会随着版本的变化而变化。

from django.test import TestCase, Client


def TestCase():
   client.login(username=self.username, password=self.password)

替代解决方案

但是,如果您必须复制和管理 client.login 正在执行的业务逻辑,这里有一些来自 Django 的业务逻辑:

    def login(self, **credentials):
        """
        Set the Factory to appear as if it has successfully logged into a site.
        Return True if login is possible or False if the provided credentials
        are incorrect.
        """
        from django.contrib.auth import authenticate
        user = authenticate(**credentials)
        if user:
            self._login(user)
            return True
        return False

    def force_login(self, user, backend=None):
        def get_backend():
            from django.contrib.auth import load_backend
            for backend_path in settings.AUTHENTICATION_BACKENDS:
                backend = load_backend(backend_path)
                if hasattr(backend, 'get_user'):
                    return backend_path

        if backend is None:
            backend = get_backend()
        user.backend = backend
        self._login(user, backend)

    def _login(self, user, backend=None):
        from django.contrib.auth import login

        # Create a fake request to store login details.
        request = HttpRequest()
        if self.session:
            request.session = self.session
        else:
            engine = import_module(settings.SESSION_ENGINE)
            request.session = engine.SessionStore()
        login(request, user, backend)
        # Save the session values.
        request.session.save()
        # Set the cookie to represent the session.
        session_cookie = settings.SESSION_COOKIE_NAME
        self.cookies[session_cookie] = request.session.session_key
        cookie_data = {
            'max-age': None,
            'path': '/',
            'domain': settings.SESSION_COOKIE_DOMAIN,
            'secure': settings.SESSION_COOKIE_SECURE or None,
            'expires': None,
        }
        self.cookies[session_cookie].update(cookie_data)

参考资料:

Django 客户端登录:https://github.com/django/django/blob/main/django/test/client.py#L596-L646

【讨论】:

  • 我接受了这个作为答案。我不确定什么是不工作的 - 因为我已经尝试过 client.login() 方法,但我一定有别的东西把它搞砸并摆脱了糟糕的代码。它开始使用client.login(),所以这似乎是解决方案。感谢您的详细解释!
猜你喜欢
  • 1970-01-01
  • 2020-05-21
  • 2021-07-27
  • 2019-01-09
  • 2020-12-14
  • 1970-01-01
  • 1970-01-01
  • 2014-04-20
  • 2013-11-16
相关资源
最近更新 更多