Skip to content

Commit

Permalink
Merge pull request #3578 from unicef/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
robertavram authored Sep 23, 2023
2 parents 5811343 + da82a95 commit 8421ff3
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/etools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VERSION = __version__ = '11'
VERSION = __version__ = '11.0.1'
NAME = 'eTools'
4 changes: 3 additions & 1 deletion src/etools/applications/attachments/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.auth.models import AnonymousUser
from django.urls import resolve, reverse

from rest_framework import status
Expand Down Expand Up @@ -125,8 +126,9 @@ def test_unauthenticated_user_forbidden(self):
factory = APIRequestFactory()
view_info = resolve(self.url)
request = factory.get(self.url)
request.user = AnonymousUser()
response = view_info.func(request)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_302_FOUND)

def test_non_schema_user(self):
user = UserFactory(profile=None, realms__data=[])
Expand Down
2 changes: 2 additions & 0 deletions src/etools/applications/core/tests/cases.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ObjectDoesNotExist
from django.core.management import call_command
from django.db import connection
Expand Down Expand Up @@ -122,6 +123,7 @@ def forced_auth_req(self, method, url, user=None, data=None, request_format='jso
request.tenant = user.profile.country

user = user or self.user
request.user = user if user else AnonymousUser()
force_authenticate(request, user=user)

if "view" in kwargs:
Expand Down
74 changes: 73 additions & 1 deletion src/etools/applications/core/urlresolvers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from functools import cached_property
from importlib import import_module

from django.conf import settings
from django.db import connection
from django.urls import reverse
from django.urls import include, reverse, URLPattern, URLResolver

from django_tenants.utils import get_tenant_model

Expand Down Expand Up @@ -29,3 +32,72 @@ def build_frontend_url(*parts, user=None):
frontend_url = update_url_with_kwargs(frontend_url, next=change_country_view)

return frontend_url


class DecoratedPatterns(object):
"""
A wrapper for an urlconf that applies a decorator to all its views.
Taken from https://github.com/twidi/django-decorator-include
"""
def __init__(self, urlconf_module, decorators):
self.urlconf = urlconf_module
try:
iter(decorators)
except TypeError:
decorators = [decorators]
self.decorators = decorators

def decorate_pattern(self, pattern):
if isinstance(pattern, URLResolver):
decorated = URLResolver(
pattern.pattern,
DecoratedPatterns(pattern.urlconf_module, self.decorators),
pattern.default_kwargs,
pattern.app_name,
pattern.namespace,
)
else:
callback = pattern.callback
for decorator in reversed(self.decorators):
callback = decorator(callback)
decorated = URLPattern(
pattern.pattern,
callback,
pattern.default_args,
pattern.name,
)
return decorated

@cached_property
def urlpatterns(self):
# urlconf_module might be a valid set of patterns, so we default to it.
patterns = getattr(self.urlconf_module, 'urlpatterns', self.urlconf_module)
return [self.decorate_pattern(pattern) for pattern in patterns]

@cached_property
def urlconf_module(self):
if isinstance(self.urlconf, str):
return import_module(self.urlconf)
else:
return self.urlconf

@cached_property
def app_name(self):
return getattr(self.urlconf_module, 'app_name', None)


def decorator_include(decorators, arg, namespace=None):
"""
Works like ``django.conf.urls.include`` but takes a view decorator
or an iterable of view decorators as the first argument and applies them,
in reverse order, to all views in the included urlconf.
"""
if isinstance(arg, tuple) and len(arg) == 3 and not isinstance(arg[0], str):
# Special case where the function is used for something like `admin.site.urls`, which
# returns a tuple with the object containing the urls, the app name, and the namespace
# `include` does not support this pattern (you pass directly `admin.site.urls`, without
# using `include`) but we have to
urlconf_module, app_name, namespace = arg
else:
urlconf_module, app_name, namespace = include(arg, namespace=namespace)
return DecoratedPatterns(urlconf_module, decorators), app_name, namespace
2 changes: 2 additions & 0 deletions src/etools/applications/field_monitoring/planning/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ def destination_str(self):
@cached_property
def country_pmes(self):
return get_user_model().objects.filter(
profile__country=connection.tenant
).filter(
realms__group__name=PME.name,
realms__country=connection.tenant,
realms__is_active=True
Expand Down
2 changes: 1 addition & 1 deletion src/etools/applications/partners/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ class InterventionAdmin(
search_fields = (
'number',
'title',
'agreement__partner__name'
'agreement__partner__organization__name'
)
readonly_fields = (
'total_budget',
Expand Down
13 changes: 8 additions & 5 deletions src/etools/applications/users/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.models import Group
from django.db import connection, router, transaction
from django.forms import Select
from django.http.response import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
Expand Down Expand Up @@ -338,13 +337,17 @@ def update_hact(self, request, pk):

class MultipleRealmForm(forms.ModelForm):
user = forms.ModelMultipleChoiceField(
widget=widgets.ManyToManyRawIdWidget(Realm._meta.get_field("user").remote_field, admin.site), queryset=get_user_model().objects.all())
widget=widgets.ManyToManyRawIdWidget(Realm._meta.get_field("user").remote_field, admin.site),
queryset=get_user_model().objects.all())
country = forms.ModelMultipleChoiceField(
widget=widgets.ManyToManyRawIdWidget(Realm._meta.get_field("country").remote_field, admin.site), queryset=Country.objects.all())
widget=widgets.ManyToManyRawIdWidget(Realm._meta.get_field("country").remote_field, admin.site),
queryset=Country.objects.all())
group = forms.ModelMultipleChoiceField(
widget=widgets.ManyToManyRawIdWidget(Realm._meta.get_field("group").remote_field, admin.site), queryset=Group.objects.all())
widget=widgets.ManyToManyRawIdWidget(Realm._meta.get_field("group").remote_field, admin.site),
queryset=Group.objects.all())
organization = forms.ModelChoiceField(
widget=Select(), queryset=Organization.objects.all())
widget=widgets.ForeignKeyRawIdWidget(Realm._meta.get_field("organization").remote_field, admin.site),
queryset=Organization.objects.all())

class Meta:
model = Realm
Expand Down
4 changes: 3 additions & 1 deletion src/etools/config/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.decorators import login_required
from django.urls import include, re_path
from django.views.generic import TemplateView

from rest_framework_nested import routers
from rest_framework_swagger.renderers import OpenAPIRenderer

from etools.applications.core.schemas import get_schema_view, get_swagger_view
from etools.applications.core.urlresolvers import decorator_include
from etools.applications.core.views import IssueJWTRedirectView, logout_view, MainView, SocialLogoutView
from etools.applications.locations.views import (
CartoDBTablesView,
Expand Down Expand Up @@ -102,7 +104,7 @@
re_path(r'^api/v2/funds/', include('etools.applications.funds.urls')),
re_path(r'^api/v2/activity/', include('unicef_snapshot.urls')),
re_path(r'^api/v2/environment/', include('etools.applications.environment.urls_v2')),
re_path(r'^api/v2/attachments/', include('unicef_attachments.urls')),
re_path(r'^api/v2/attachments/', decorator_include(login_required, include('unicef_attachments.urls'))),

# *************** API version 3 ******************
re_path(r'^api/v3/users/', include('etools.applications.users.urls_v3', namespace='users_v3')),
Expand Down

0 comments on commit 8421ff3

Please sign in to comment.