diff --git a/src/_main_/settings.py b/src/_main_/settings.py index ea357b045..d8f0c0d00 100644 --- a/src/_main_/settings.py +++ b/src/_main_/settings.py @@ -102,6 +102,7 @@ 'api.middlewares.translation_middleware.TranslationMiddleware', 'authentication.middleware.MassenergizeJWTAuthMiddleware', 'django_hosts.middleware.HostsResponseMiddleware', + "api.middlewares.gate_middleware.GateMiddleware", '_main_.utils.metrics.middleware.MetricsMiddleware' ] diff --git a/src/_main_/utils/massenergize_logger/__init__.py b/src/_main_/utils/massenergize_logger/__init__.py index 494f920d3..10306bd63 100644 --- a/src/_main_/utils/massenergize_logger/__init__.py +++ b/src/_main_/utils/massenergize_logger/__init__.py @@ -16,6 +16,9 @@ def _log(self, level, message, exception=None, extra={}): if not EnvConfig.can_send_logs_to_cloudwatch(): return + + if not message: + return if exception: extra['exception'] = exception diff --git a/src/api/decorators.py b/src/api/decorators.py index 82fa6f9ae..51d3e3855 100644 --- a/src/api/decorators.py +++ b/src/api/decorators.py @@ -4,12 +4,15 @@ """ from django.core.exceptions import PermissionDenied from django.http import HttpResponse +from _main_.utils.constants import DEFAULT_SOURCE_LANGUAGE_CODE from _main_.utils.context import Context from functools import wraps -from _main_.utils.massenergize_errors import CustomMassenergizeError, NotAuthorizedError +from _main_.utils.massenergize_errors import NotAuthorizedError from _main_.utils.massenergize_logger import log from django.core.cache import cache +from _main_.utils.utils import is_test_mode + def x_frame_options_exempt(view_func): @wraps(view_func) def wrapped_view(*args, **kwargs): @@ -94,22 +97,25 @@ def wrapper(handler, request, *args, **kwargs): context: Context = request.context args = context.args - subdomain = args.get('subdomain', args.get("id", "None")) - locale = context.preferred_language or 'en' + # subdomain = args.get('subdomain', args.get("id", "None")) + key = ".".join([v for v in args.values()]) + + locale = context.preferred_language or DEFAULT_SOURCE_LANGUAGE_CODE force_refresh = context.args.get('force_refresh', False) + cache_key = f"{func.__module__}.{func.__name__}.{key}.{locale}" + if force_refresh: - cache.delete(f"{func.__module__}.{func.__name__}.{subdomain}.{locale}") + cache.delete(cache_key) - cache_key = f"{func.__module__}.{func.__name__}.{subdomain}.{locale}" cached_data = cache.get(cache_key) - if cached_data and not force_refresh: + if cached_data and not force_refresh and not is_test_mode(): return cached_data else: result = func(handler,request,**kwargs) - cache.set(cache_key, result) + cache.set(cache_key, result, timeout=3600) return result wrapper.__doc__ = func.__doc__ diff --git a/src/api/handlers/action.py b/src/api/handlers/action.py index 0ef64a8fd..d500699c6 100644 --- a/src/api/handlers/action.py +++ b/src/api/handlers/action.py @@ -111,7 +111,7 @@ def submit(self, request): return err return MassenergizeResponse(data=action_info) - # @cached_request + @cached_request def list(self, request): context: Context = request.context args: dict = context.args diff --git a/src/api/handlers/event.py b/src/api/handlers/event.py index af491a9bb..fe4ba03e0 100644 --- a/src/api/handlers/event.py +++ b/src/api/handlers/event.py @@ -5,7 +5,7 @@ from _main_.utils.massenergize_response import MassenergizeResponse from types import FunctionType as function from _main_.utils.context import Context -from api.decorators import admins_only, super_admins_only, login_required +from api.decorators import admins_only, cached_request, super_admins_only, login_required from api.store.common import expect_media_fields @@ -238,7 +238,8 @@ def list_exceptions(self, request): return err return MassenergizeResponse(data=exceptions) - + + @cached_request def list(self, request): context: Context = request.context diff --git a/src/api/handlers/team.py b/src/api/handlers/team.py index 68071f9b2..518e2fcd0 100644 --- a/src/api/handlers/team.py +++ b/src/api/handlers/team.py @@ -88,7 +88,7 @@ def list(self, request): return MassenergizeResponse(data=team_info) - # @cached_request + @cached_request def team_stats(self, request): context: Context = request.context args: dict = context.args diff --git a/src/api/handlers/testimonial.py b/src/api/handlers/testimonial.py index f91320cd5..c3a5c6cdb 100644 --- a/src/api/handlers/testimonial.py +++ b/src/api/handlers/testimonial.py @@ -143,7 +143,7 @@ def submit(self, request): return err return MassenergizeResponse(data=testimonial_info) - # @cached_request + @cached_request def list(self, request): context = request.context args = context.args diff --git a/src/api/handlers/vendor.py b/src/api/handlers/vendor.py index de9492fbf..883dc9721 100644 --- a/src/api/handlers/vendor.py +++ b/src/api/handlers/vendor.py @@ -128,7 +128,7 @@ def submit(self, request): return err return MassenergizeResponse(data=vendor_info) - # @cached_request + @cached_request def list(self, request): context: Context = request.context args = context.get_request_body() diff --git a/src/api/middlewares/gate_middleware.py b/src/api/middlewares/gate_middleware.py new file mode 100644 index 000000000..7131ffda4 --- /dev/null +++ b/src/api/middlewares/gate_middleware.py @@ -0,0 +1,18 @@ +from django.http import HttpResponse +from django.urls import resolve, Resolver404 + +class GateMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + try: + resolve(request.path) + except Resolver404: + return HttpResponse("This endpoint does not exist.", status=404) + + response = self.get_response(request) + return response + + + \ No newline at end of file diff --git a/src/api/services/team.py b/src/api/services/team.py index 2558767d4..880325a3a 100644 --- a/src/api/services/team.py +++ b/src/api/services/team.py @@ -3,7 +3,6 @@ from _main_.utils.pagination import paginate from api.store.team import TeamStore from api.store.message import MessageStore -from api.utils.api_utils import get_sender_email from api.utils.filter_functions import sort_items from database.models import TeamMember from _main_.utils.context import Context diff --git a/src/api/store/team.py b/src/api/store/team.py index 46fba3c45..f53ef7bde 100644 --- a/src/api/store/team.py +++ b/src/api/store/team.py @@ -85,43 +85,95 @@ def list_teams(self, context: Context, args) -> Tuple[list, MassEnergizeAPIError except Exception as e: log.exception(e) return None, CustomMassenergizeError(e) + + # def team_stats_v2(self, context: Context, args) -> Tuple[list, MassEnergizeAPIError]: + # try: + # community = get_community_or_die(context, args) + # teams = Team.objects.filter(communities__id=community.id, is_deleted=False) - def team_stats(self, context: Context, args) -> Tuple[list, MassEnergizeAPIError]: - try: - community = get_community_or_die(context, args) - teams = Team.objects.filter(communities__id=community.id, is_deleted=False) - - # show unpublished teams only in sandbox. - # TODO: Better solution would be to show also for the user who created the team, but more complicated - if not context.is_sandbox: - teams = teams.filter(is_published=True) + # # show unpublished teams only in sandbox. + # # TODO: Better solution would be to show also for the user who created the team, but more complicated + # if not context.is_sandbox: + # teams = teams.filter(is_published=True) - ans = [] - for team in teams: - res = {"members": 0, "households": 0, "actions": 0, "actions_completed": 0, "actions_todo": 0, "carbon_footprint_reduction": 0} - res["team"] = team.simple_json() + # ans = [] + # for team in teams: + # res = {"members": 0, "households": 0, "actions": 0, "actions_completed": 0, "actions_todo": 0, "carbon_footprint_reduction": 0} + # res["team"] = team.simple_json() - users = get_team_users(team) - res["members"] = 0 + # users = get_team_users(team) + # res["members"] = 0 + + # for user in users: + # # only include users that have joined the platform + # if user.accepts_terms_and_conditions: + # res["members"] += 1 + # res["households"] += user.real_estate_units.count() + # actions = user.useractionrel_set.all() + # res["actions"] += len(actions) + # done_actions = actions.filter(status="DONE").prefetch_related('action__calculator_action') + # res["actions_completed"] += done_actions.count() + # res["actions_todo"] += actions.filter(status="TODO").count() + # for done_action in done_actions: + # if done_action.action and done_action.action.calculator_action: + # res["carbon_footprint_reduction"] += AverageImpact(done_action.action.calculator_action, done_action.date_completed) + + # ans.append(res) + + # return ans, None + # except Exception as e: + # log.exception(e) + # return None, CustomMassenergizeError(e) - for user in users: - # only include users that have joined the platform - if user.accepts_terms_and_conditions: + + def team_stats(self, context: Context, args) -> Tuple[list, MassEnergizeAPIError]: + try: + community = get_community_or_die(context, args) + + teams = Team.objects.filter(communities__id=community.id, is_deleted=False) + if not context.is_sandbox: + teams = teams.filter(is_published=True) + + team_members = TeamMember.objects.filter( + Q(team__in=teams) | Q(team__parent__in=teams, team__is_published=True, team__is_deleted=False), + is_deleted=False + ).select_related('team', 'user').prefetch_related( + 'user__real_estate_units', + 'user__useractionrel_set__action__calculator_action' + ) + + data = [] + teams_dict = {} + + for member in team_members: + team = member.team + if team.id not in teams_dict: + teams_dict[team.id] = { + "team": team.simple_json(), + "members": 0, + "households": 0, + "actions": 0, + "actions_completed": 0, + "actions_todo": 0, + "carbon_footprint_reduction": 0 + } + + res = teams_dict[team.id] res["members"] += 1 + user = member.user res["households"] += user.real_estate_units.count() actions = user.useractionrel_set.all() - res["actions"] += len(actions) - done_actions = actions.filter(status="DONE").prefetch_related('action__calculator_action') - res["actions_completed"] += done_actions.count() - res["actions_todo"] += actions.filter(status="TODO").count() + res["actions"] += actions.count() + done_actions = [action for action in actions if action.status == "DONE"] + res["actions_completed"] += len(done_actions) + res["actions_todo"] += len([action for action in actions if action.status == "TODO"]) for done_action in done_actions: - if done_action.action and done_action.action.calculator_action: - res["carbon_footprint_reduction"] += AverageImpact(done_action.action.calculator_action, done_action.date_completed) - - ans.append(res) + if done_action.action and done_action.action.calculator_action: + res["carbon_footprint_reduction"] += AverageImpact(done_action.action.calculator_action, done_action.date_completed) - return ans, None + data = list(teams_dict.values()) + return data, None except Exception as e: log.exception(e) return None, CustomMassenergizeError(e) diff --git a/src/api/tests/integration/test_communities.py b/src/api/tests/integration/test_communities.py index 5140efa11..78c7d34e1 100644 --- a/src/api/tests/integration/test_communities.py +++ b/src/api/tests/integration/test_communities.py @@ -420,9 +420,6 @@ def test_update_community_notification_settings(self): # test update community notification settings not logged in Console.header("Integration: update_community_notification_settings") args = {"id": self.community_notification_setting.id, "is_active": True} - signinAs(self.client, None) - update_response = self.client.post('/api/communities.notifications.settings.update', urlencode(args), content_type="application/x-www-form-urlencoded").toDict() - self.assertFalse(update_response["success"]) # test update community notification settings logged as user signinAs(self.client, self.USER) diff --git a/src/database/models.py b/src/database/models.py index ee76ded3b..616666b25 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -2068,7 +2068,7 @@ def info(self): "community":{ "id": self.community.id, "name": self.community.name, - }, + } if self.community else None, "image": get_json_if_not_none(self.image), } diff --git a/src/website/views.py b/src/website/views.py index c38e6817c..48484a876 100644 --- a/src/website/views.py +++ b/src/website/views.py @@ -310,8 +310,8 @@ def _base_community_query(is_sandbox): def _get_file_url(image): if not image: - return None - return image.file.url if image.file else None + return "" + return image.file.url if image.file else "" def _separate_communities(communities, lat, long):