diff --git a/django_project_base/account/middleware.py b/django_project_base/account/middleware.py index 3a0b749d..220d7494 100644 --- a/django_project_base/account/middleware.py +++ b/django_project_base/account/middleware.py @@ -69,10 +69,8 @@ def process_request(self, request): else: curr_project_slug = request.session.get(current_project_attr, None) - request.selected_project_slug = curr_project_slug request.selected_project = SimpleLazyObject(load_selected_project(curr_project_slug)) else: - request.selected_project_slug = SimpleLazyObject(selected_project_not_setup) request.selected_project = SimpleLazyObject(selected_project_not_setup) def process_response(self, request, response): diff --git a/django_project_base/caching/cache_queue/__init__.py b/django_project_base/caching/cache_queue/__init__.py index 92077305..5b900ec6 100644 --- a/django_project_base/caching/cache_queue/__init__.py +++ b/django_project_base/caching/cache_queue/__init__.py @@ -20,31 +20,31 @@ def __init__(self, key, cache_name, timeout): @abstractmethod def set_cache(self): - """Set cache client""" + """ Set cache client """ @abstractmethod def rpush(self, *values): - """Add data to end of queue""" + """ Add data to end of queue """ @abstractmethod def lpush(self, *values): - """Add data to start of queue""" + """ Add data to start of queue """ @abstractmethod def lpop(self, count: Optional[int] = None): - """Get and remove data from start of queue""" + """ Get and remove data from start of queue """ @abstractmethod def rpop(self, count: Optional[int] = None): - """Get and remove data from end of queue""" + """ Get and remove data from end of queue """ @abstractmethod def lrange(self, count=None): - """Get data from start of queue""" + """ Get data from start of queue """ @abstractmethod def ltrim(self, count=None): - """Remove data from start of queue""" + """ Remove data from start of queue """ def get_default_timeout(self): return self.django_cache.default_timeout @@ -56,7 +56,7 @@ def set_timeout(self, timeout): @abstractmethod def update_timeout(self): - """Update timeout of cached key""" + """ Update timeout of cached key """ @staticmethod def is_redis_cache_backend(cache_name): diff --git a/django_project_base/notifications/models.py b/django_project_base/notifications/models.py index 3ef98575..fa2eb579 100644 --- a/django_project_base/notifications/models.py +++ b/django_project_base/notifications/models.py @@ -166,12 +166,13 @@ def get_queryset(self, **kwargs): from django_project_base.base.middleware import get_current_request request = get_current_request() - if request and getattr(request, "selected_project_slug", None): - slug = request.selected_project_slug + if request and getattr(request, "selected_project", None): + slug = request.selected_project.slug except Exception: pass else: - slug = getattr(kwargs["request"], "selected_project_slug", "") + project = getattr(kwargs["request"], "selected_project", None) + slug = project.slug if project else "" if not slug: return [] diff --git a/django_project_base/notifications/rest/notification.py b/django_project_base/notifications/rest/notification.py index 10d28d30..364b1afb 100644 --- a/django_project_base/notifications/rest/notification.py +++ b/django_project_base/notifications/rest/notification.py @@ -472,7 +472,7 @@ class NewMessageSerializer(Serializer): def get_queryset(self): try: return DjangoProjectBaseNotification.objects.filter( - project_slug=self.request.selected_project_slug + project_slug=self.request.selected_project.slug ).order_by("-created_at") except ProjectNotSelectedError as e: raise NotFound(e.message) @@ -487,9 +487,7 @@ def _create_notification(self, serializer): content_type=DjangoProjectBaseMessage.HTML, ), raw_recipents=self.request.data["message_to"], - project=swapper.load_model("django_project_base", "Project") - .objects.get(slug=self.request.selected_project_slug) - .slug, + project=self.request.selected_project.slug, recipients=serializer.validated_data["message_to"], delay=int(datetime.datetime.now().timestamp()), channels=[ diff --git a/django_project_base/rest/project.py b/django_project_base/rest/project.py index cd27b424..1929225b 100644 --- a/django_project_base/rest/project.py +++ b/django_project_base/rest/project.py @@ -356,7 +356,7 @@ def get_queryset(self): try: return ( self.get_serializer() - .Meta.model.objects.filter(project__slug=self.request.selected_project.slug) + .Meta.model.objects.filter(project=self.request.selected_project) .exclude(value_type=BaseProjectSettings.VALUE_TYPE_CUSTOM) .order_by("name") ) diff --git a/docs/project.md b/docs/project.md index 5a1f9885..a1f74c59 100644 --- a/docs/project.md +++ b/docs/project.md @@ -38,14 +38,13 @@ See [URL variables Middleware](./url-variables-middleware) for more information. Having specified the `DJANGO_PROJECT_BASE_BASE_REQUEST_URL_VARIABLES` setting, specifically the "project" section, [SessionMiddleware](./authentication#session-middleware) will be keeping track of currently selected project and the -following variables will become available for use: +following variable will become available for use: ```python request.selected_project -request.selected_project_slug ``` -The former is a `SimpleLazyObject` resolving to currently selected project. The information is also written in the +Is is a `SimpleLazyObject` resolving to currently selected project. The information is also written in the session data, so not every API call needs to bear project slug in order for the system to know it. If selected_project cannot evaluate, either because the setting is not set or because the project can't be found, @@ -58,15 +57,3 @@ have to access one of its members, e.g. `request.selected_project.get_deferred_fields() # force immediate LazyObject evaluation`. If you're trying to catch the exception, make sure you force evaluation. There are examples for both forced and "natural" evaluation in DPB code. -The latter (`request.selected_project_slug`) is currently evaluated project slug. - -::: warning -`request.selected_project_slug` may not represent a proper project slug. It is just what was evaluated as per -`DJANGO_PROJECT_BASE_BASE_REQUEST_URL_VARIABLES` setting. If you need a validated slug, use `request.selected_project` -and access it's `slug` field. -::: - -::: info -[Task #705](https://taiga.velis.si/project/velis-django-project-admin/us/705) will provide means to annul the above -warning. -::: diff --git a/docs/url-variables-middleware.md b/docs/url-variables-middleware.md index eeb3201a..5e939648 100644 --- a/docs/url-variables-middleware.md +++ b/docs/url-variables-middleware.md @@ -15,7 +15,7 @@ First, the global request API: from django_project_base.base.middleware import has_current_request, get_current_request if has_current_request(): - print(get_current_request().selected_project_slug) + print(get_current_request().selected_project.slug) ``` The middleware is configured with a setting named `DJANGO_PROJECT_BASE_BASE_REQUEST_URL_VARIABLES`. Default value for diff --git a/requirements.txt b/requirements.txt index 6f73ec96..25ebf9cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,4 @@ pandas click>=8.1 ruff ruff-lsp +django_redis diff --git a/tests/test_project.py b/tests/test_project.py index 2af63a97..5e382788 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -52,7 +52,7 @@ def test_update_project(self): project: Model = ProjectSerializer.Meta.model.objects.last() update_project: Response = self.api_client.patch(f"{self.url}/{project.pk}", {"name": "updated-name"}, - headers={"Current-project": project.slug}) + HTTP_CURRENT_PROJECT=project.slug) self.assertEqual(update_project.status_code, status.HTTP_200_OK) project.refresh_from_db() self.assertEqual(project.name, "updated-name") diff --git a/tests/test_user_profile.py b/tests/test_user_profile.py index cdaf4226..5eed9675 100644 --- a/tests/test_user_profile.py +++ b/tests/test_user_profile.py @@ -49,12 +49,29 @@ def test_insert_profile(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_update_profile(self): - # TODO: Tole ne dela, ker miha nima povezanih projektov... + from django_project_base.rest.project import ProjectSerializer + import swapper + self.assertTrue(self.api_client.login(username="miha", password="mihamiha"), "Not logged in") - response = self.api_client.patch("/account/profile/1", {"bio": "Sample bio text."}, format="json") + project = ProjectSerializer.Meta.model.objects.last() + swapper.load_model( + "django_project_base", "ProjectMember" + ).objects.create(project_id=project.id, member_id=1) + + response = self.api_client.patch( + "/account/profile/1", + {"bio": "Sample bio text."}, + format="json", + HTTP_CURRENT_PROJECT=project.slug + ) self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.api_client.get("/account/profile/1", {}, format="json") + response = self.api_client.get( + "/account/profile/1", + {}, + format="json", + HTTP_CURRENT_PROJECT=project.slug + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get("bio", False), "Sample bio text.") @@ -64,31 +81,70 @@ def test_search_url_is_disabled(self): self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_search_query(self): - # TODO: Tole ne dela, ker miha nima povezanih projektov... + from django_project_base.rest.project import ProjectSerializer + import swapper self.assertTrue(self.api_client.login(username="miha", password="mihamiha"), "Not logged in") - response = self.api_client.get("/account/profile?search=mi", {}, format="json") + project = ProjectSerializer.Meta.model.objects.last() + swapper.load_model( + "django_project_base", "ProjectMember" + ).objects.create(project_id=project.id, member_id=1) + + response = self.api_client.get( + "/account/profile?search=mi", + {}, + format="json", + HTTP_CURRENT_PROJECT=project.slug + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) self.assertEqual(response.data[0]["full_name"], "Miha Novak") + # Change with afca5415 - "only show users of current project or none at all" 22.12.2023 # we cannot query other users if we are not admins - response = self.api_client.get("/account/profile?search=j", {}, format="json") - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 0) - - miha = UserProfile.objects.get(username="miha") - miha.is_staff = True - miha.save() - user_cache_invalidate(miha) - response = self.api_client.get("/account/profile?search=j", {}, format="json") - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]["full_name"], "Janez Novak") + # response = self.api_client.get( + # "/account/profile?search=j", + # {}, + # format="json", + # HTTP_CURRENT_PROJECT=project.slug + # ) + # self.assertEqual(response.status_code, status.HTTP_200_OK) + # self.assertEqual(len(response.data), 0) + # + # miha = UserProfile.objects.get(username="miha") + # miha.is_staff = True + # miha.save() + # user_cache_invalidate(miha) + # response = self.api_client.get( + # "/account/profile?search=j", + # {}, + # format="json", + # HTTP_CURRENT_PROJECT=project.slug + # ) + # + # self.assertEqual(response.status_code, status.HTTP_200_OK) + # self.assertEqual(len(response.data), 1) + # self.assertEqual(response.data[0]["full_name"], "Janez Novak") def test_supperss_is_staff_is_superuser(self): # TODO: Tole ne dela, ker miha nima povezanih projektov... + from django_project_base.rest.project import ProjectSerializer + import swapper + self.assertTrue(self.api_client.login(username="miha", password="mihamiha"), "Not logged in") - response = self.api_client.get("/account/profile/1", {}, format="json") + project = ProjectSerializer.Meta.model.objects.last() + swapper.load_model( + "django_project_base", "ProjectMember" + ).objects.create(project_id=project.id, member_id=1) + swapper.load_model( + "django_project_base", "ProjectMember" + ).objects.create(project_id=project.id, member_id=2) + + response = self.api_client.get( + "/account/profile/1", + {}, + format="json", + HTTP_CURRENT_PROJECT=project.slug + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get("is_staff", "not_exist"), "not_exist") self.assertEqual(response.data.get("is_superuser", "not_exist"), "not_exist") @@ -99,23 +155,40 @@ def test_supperss_is_staff_is_superuser(self): miha.save() user_cache_invalidate(miha) - response = self.api_client.get("/account/profile/1", {}, format="json") + response = self.api_client.get( + "/account/profile/1", + {}, + format="json", + HTTP_CURRENT_PROJECT=project.slug + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get("is_staff", "not_exist"), True) self.assertEqual(response.data.get("is_superuser", "not_exist"), True) - response = self.api_client.get("/account/profile/2", {}, format="json") + response = self.api_client.get( + "/account/profile/2", + {}, + format="json", + HTTP_CURRENT_PROJECT=project.slug + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get("is_staff", "not_exist"), False) self.assertEqual(response.data.get("is_superuser", "not_exist"), False) def test_profile_destroy(self): from django_project_base.rest.project import ProjectSerializer + import swapper self.assertTrue(self.api_client.login(username="miha", password="mihamiha"), "Not logged in") project = ProjectSerializer.Meta.model.objects.last() - response = self.api_client.delete("/account/profile/1", {}, format="json", - headers={"Current-project": project.slug}) + swapper.load_model( + "django_project_base", "ProjectMember" + ).objects.create(project_id=project.id, member_id=2) + response = self.api_client.delete( + "/account/profile/1", {}, + format="json", + HTTP_CURRENT_PROJECT=project.slug + ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) expected_response = b'{"detail":"You do not have permission to perform this action."}' self.assertEqual(response.content, expected_response) @@ -125,11 +198,12 @@ def test_profile_destroy(self): miha.is_superuser = True miha.save() user_cache_invalidate(miha) - # TODO: Tole odleti, ker user ni vezan na (noben) projekt... in takega userja trenutno sploh ne moreš izbrisati. - # Kaj je treba tukaj sploh narediti... glede na to, da je user, ki briše "superuser" najbrž tega preverjanja ne - # rabim - response = self.api_client.delete("/account/profile/2", {}, format="json", - headers={"Current-project": project.slug}) + response = self.api_client.delete( + "/account/profile/2", + {}, + format="json", + HTTP_CURRENT_PROJECT=project.slug + ) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) def test_profile_destroy_my_profile(self):