From c652752e8f3bb52071d269691a09bbd4850747ba Mon Sep 17 00:00:00 2001 From: Domejko Date: Fri, 12 Apr 2024 13:07:27 +0200 Subject: [PATCH 01/12] Mypy type errors fix --- backend/types/emails.py | 2 +- pyproject.toml | 5 ++++- settings/helpers.py | 18 ++++++++---------- settings/local_settings.py | 6 ++++-- settings/prod_settings.py | 2 +- settings/settings.py | 8 ++++---- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/backend/types/emails.py b/backend/types/emails.py index 50e5a9af..a7e5af17 100644 --- a/backend/types/emails.py +++ b/backend/types/emails.py @@ -14,7 +14,7 @@ class SingleEmailInput: destination: str | list[str] subject: str content: str | SingleTemplatedEmailContent - ConfigurationSetName: str | None = None + ConfigurationSetName: str from_address: str | None = None from_address_name_prefix: str | None = None diff --git a/pyproject.toml b/pyproject.toml index e1a4bb98..1a47ab14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,6 +134,9 @@ module = [ "forex_python.converter", "login_required", "storages.backends.s3", - "social_django.models" + "social_django.models", + "emails", + "step_functions", + "django_components" ] ignore_missing_imports = true diff --git a/settings/helpers.py b/settings/helpers.py index 58016422..371c47da 100644 --- a/settings/helpers.py +++ b/settings/helpers.py @@ -5,7 +5,7 @@ import sys from dataclasses import dataclass from logging import exception -from typing import Literal, List +from typing import Literal, List, Any from collections.abc import Sequence from typing import Optional @@ -105,7 +105,7 @@ def send_email(data: SingleEmailInput) -> SingleEmailSuccessResponse | SingleEma try: if isinstance(data.content, dict): - data_str: str = ( + data_str: str | Any = ( data.content.get("template_data") if isinstance(data.content.get("template_data"), str) else json.dumps(data.content.get("template_data")) @@ -114,17 +114,17 @@ def send_email(data: SingleEmailInput) -> SingleEmailSuccessResponse | SingleEma from_email_address: str = str(data.from_address_name_prefix) if data.from_address_name_prefix else "" from_email_address += str(data.from_address or get_var("AWS_SES_FROM_ADDRESS")) - response: SendEmailResponseTypeDef = EMAIL_CLIENT.send_email( + response = EMAIL_CLIENT.send_email( FromEmailAddress=from_email_address, Destination={"ToAddresses": data.destination}, - Content={"Template": {"TemplateName": data.content.get("template_name"), "TemplateData": data_str}}, + Content={"Template": {"TemplateName": data.content.get("template_name"), "TemplateData": data_str}}, # type: ignore ConfigurationSetName=data.ConfigurationSetName or "", ) else: - from_email_address: str = str(data.from_address_name_prefix) if data.from_address_name_prefix else "" + from_email_address = str(data.from_address_name_prefix) if data.from_address_name_prefix else "" from_email_address += str(data.from_address or get_var("AWS_SES_FROM_ADDRESS")) - response: SendEmailResponseTypeDef = EMAIL_CLIENT.send_email( + response = EMAIL_CLIENT.send_email( FromEmailAddress=from_email_address, Destination={"ToAddresses": data.destination}, Content={"Simple": {"Subject": {"Data": data.subject}, "Body": {"Text": {"Data": data.content}}}}, @@ -152,7 +152,7 @@ def send_templated_bulk_email(data: BulkTemplatedEmailInput) -> BulkEmailSuccess for entry in data.email_list: destination: list[str] = [entry.destination] if not isinstance(entry.destination, list) else entry.destination - data_str = entry.template_data if isinstance(entry.template_data, str) else json.dumps(entry.template_data) + data_str: str = entry.template_data if isinstance(entry.template_data, str) else json.dumps(entry.template_data) entries.append( { @@ -162,9 +162,7 @@ def send_templated_bulk_email(data: BulkTemplatedEmailInput) -> BulkEmailSuccess ) try: - data_str: str = ( - data.default_template_data if isinstance(data.default_template_data, str) else json.dumps(data.default_template_data) - ) + data_str = data.default_template_data if isinstance(data.default_template_data, str) else json.dumps(data.default_template_data) from_email_address: str = str(data.from_address_name_prefix) if data.from_address_name_prefix else "" from_email_address += str(data.from_address or get_var("AWS_SES_FROM_ADDRESS")) diff --git a/settings/local_settings.py b/settings/local_settings.py index d486584b..16697779 100644 --- a/settings/local_settings.py +++ b/settings/local_settings.py @@ -1,3 +1,5 @@ +from typing import Any + import environ import os from pathlib import Path @@ -15,7 +17,7 @@ DB_TYPE = "mysql" if DB_TYPE in ["mysql", "mariadb"] else DB_TYPE if DB_TYPE == "mysql" or DB_TYPE == "postgres": - DATABASES = { + DATABASES: Any = { "default": { "ENGINE": ( "django.db.backends.postgresql_psycopg2" @@ -46,7 +48,7 @@ print("[BACKEND] Using sqlite3 database", flush=True) -ALLOWED_HOSTS = ["localhost", "127.0.0.1"] +ALLOWED_HOSTS: list[str | None] = ["localhost", "127.0.0.1"] os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = ( "1" # THIS WILL ALLOW HTTP - NOT RECOMMENDED diff --git a/settings/prod_settings.py b/settings/prod_settings.py index 8276e788..96ccb714 100644 --- a/settings/prod_settings.py +++ b/settings/prod_settings.py @@ -40,6 +40,6 @@ print(f"[BACKEND] Using {DB_TYPE} database: {os.environ.get('DATABASE_NAME')}") -ALLOWED_HOSTS = [os.environ.get("URL")] +ALLOWED_HOSTS: list[str | None] = [os.environ.get("URL")] os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "0" # THIS WILL ALLOW HTTP IF IT'S SET TO 1 - NOT RECOMMENDED diff --git a/settings/settings.py b/settings/settings.py index f427f67d..c4dc21c3 100644 --- a/settings/settings.py +++ b/settings/settings.py @@ -6,7 +6,7 @@ from pathlib import Path from django.contrib.messages import constants as messages -from django.contrib.staticfiles.storage import FileSystemStorage +from django.contrib.staticfiles.storage import FileSystemStorage # type: ignore from storages.backends.s3 import S3Storage from .helpers import get_var @@ -91,7 +91,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent -EMAIL_WHITELIST = [] +EMAIL_WHITELIST: list[str] = [] AUTHENTICATION_BACKENDS = [ # "django.contrib.auth.backends.ModelBackend", "backend.auth_backends.EmailInsteadOfUsernameBackend", @@ -349,7 +349,7 @@ class CustomPrivateMediaStorage(S3Storage): MEDIA_ROOT = os.path.join(BASE_DIR, "media") DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage" - class CustomPublicMediaStorage(FileSystemStorage): # This overrides the AWS version + class CustomPublicMediaStorage(FileSystemStorage): # type: ignore # This overrides the AWS version ... @@ -359,7 +359,7 @@ class CustomPublicMediaStorage(FileSystemStorage): # This overrides the AWS ver PRIVATE_FILE_STORAGE = "settings.settings.CustomPrivateMediaStorage" else: - class CustomPrivateMediaStorage(FileSystemStorage): # This overrides the AWS version + class CustomPrivateMediaStorage(FileSystemStorage): # type: ignore # This overrides the AWS version ... PRIVATE_FILE_STORAGE = "django.core.files.storage.FileSystemStorage" From 08d6f5e53fef8092b6877db03e0a1731708c06b9 Mon Sep 17 00:00:00 2001 From: Domejko Date: Fri, 12 Apr 2024 15:51:37 +0200 Subject: [PATCH 02/12] Mypy type errors fix --- backend/admin.py | 4 +++- backend/api/healthcheck/healthcheck.py | 9 +++------ backend/context_processors.py | 6 +++--- backend/models.py | 2 +- backend/views/core/currency_converter/dashboard.py | 2 -- backend/views/core/invoices/edit.py | 8 ++++---- backend/views/core/settings/teams.py | 6 ++++-- 7 files changed, 18 insertions(+), 19 deletions(-) diff --git a/backend/admin.py b/backend/admin.py index e3c1aec4..0497d6f7 100644 --- a/backend/admin.py +++ b/backend/admin.py @@ -1,3 +1,5 @@ +from typing import Iterable, Any + from django.contrib import admin from django.contrib.auth.admin import UserAdmin @@ -73,7 +75,7 @@ class EmailSendStatusAdmin(admin.ModelAdmin): admin.site.register(EmailSendStatus, EmailSendStatusAdmin) # admin.site.unregister(User) -fields = list(UserAdmin.fieldsets) +fields = list(UserAdmin.fieldsets) # type: ignore[arg-type] fields[0] = (None, {"fields": ("username", "password", "logged_in_as_team", "awaiting_email_verification")}) UserAdmin.fieldsets = tuple(fields) admin.site.register(User, UserAdmin) diff --git a/backend/api/healthcheck/healthcheck.py b/backend/api/healthcheck/healthcheck.py index f512b307..4c1862d3 100644 --- a/backend/api/healthcheck/healthcheck.py +++ b/backend/api/healthcheck/healthcheck.py @@ -11,10 +11,7 @@ def ping(request: HttpRequest) -> HttpResponse: @login_not_required def healthcheck(request: HttpRequest) -> HttpResponse: try: - status = connection.ensure_connection() - except OperationalError: - status = "error" - - if not status: # good + connection.ensure_connection() return HttpResponse(status=200, content="All operations are up and running!") - return HttpResponse(status=503, content="Service Unavailable") + except OperationalError: + return HttpResponse(status=503, content="Service Unavailable") diff --git a/backend/context_processors.py b/backend/context_processors.py index 6d9ee2fb..d9af432e 100644 --- a/backend/context_processors.py +++ b/backend/context_processors.py @@ -52,7 +52,7 @@ def get_item(name: str, url_name: Optional[str] = None, icon: Optional[str] = No "icon": icon, } - def generate_breadcrumbs(*breadcrumb_list: str) -> List[Dict[str, Any]]: + def generate_breadcrumbs(*breadcrumb_list: str) -> List[dict[Any, Any] | None]: """ Generate a list of breadcrumb items based on the provided list of breadcrumb names. @@ -64,7 +64,7 @@ def generate_breadcrumbs(*breadcrumb_list: str) -> List[Dict[str, Any]]: """ return [all_items.get(breadcrumb) for breadcrumb in breadcrumb_list] - current_url_name: str = request.resolver_match.url_name + current_url_name: str | Any = request.resolver_match.url_name # type: ignore[union-attr] all_items: Dict[str, dict] = { "dashboard": get_item("Dashboard", "dashboard", "house"), @@ -78,7 +78,7 @@ def generate_breadcrumbs(*breadcrumb_list: str) -> List[Dict[str, Any]]: "clients create": get_item("Create", "clients create"), } - all_breadcrumbs: Dict[str, list] = { + all_breadcrumbs: Dict[str | None, list] = { "dashboard": generate_breadcrumbs("dashboard"), "user settings teams": generate_breadcrumbs("dashboard", "user settings teams"), "receipts dashboard": generate_breadcrumbs("dashboard", "receipts dashboard"), diff --git a/backend/models.py b/backend/models.py index 4c3afe98..ef9ef96e 100644 --- a/backend/models.py +++ b/backend/models.py @@ -49,7 +49,7 @@ def get_queryset(self): class User(AbstractUser): - objects = CustomUserManager() + objects = CustomUserManager() # type: ignore logged_in_as_team = models.ForeignKey("Team", on_delete=models.SET_NULL, null=True, blank=True) awaiting_email_verification = models.BooleanField(default=True) diff --git a/backend/views/core/currency_converter/dashboard.py b/backend/views/core/currency_converter/dashboard.py index 60db6a43..1846bd42 100644 --- a/backend/views/core/currency_converter/dashboard.py +++ b/backend/views/core/currency_converter/dashboard.py @@ -1,5 +1,3 @@ -import datetime - from django.http import HttpRequest from django.shortcuts import render from forex_python.converter import CurrencyRates diff --git a/backend/views/core/invoices/edit.py b/backend/views/core/invoices/edit.py index a1ade947..78513703 100644 --- a/backend/views/core/invoices/edit.py +++ b/backend/views/core/invoices/edit.py @@ -71,7 +71,7 @@ def edit_invoice(request: HttpRequest, invoice_id): except Invoice.DoesNotExist: return JsonResponse({"message": "Invoice not found"}, status=404) - if request.user.logged_in_as_team and request.user.logged_in_as_team != invoice.organization: + if request.user.logged_in_as_team and request.user.logged_in_as_team != invoice.organization: # type: ignore[union-attr] return JsonResponse( {"message": "You do not have permission to edit this invoice"}, status=403, @@ -83,7 +83,7 @@ def edit_invoice(request: HttpRequest, invoice_id): ) attributes_to_updates = { - "date_due": datetime.strptime(request.POST.get("date_due"), "%Y-%m-%d").date(), + "date_due": datetime.strptime(request.POST.get("date_due"), "%Y-%m-%d").date(), # type: ignore[arg-type] "date_issued": request.POST.get("date_issued"), "self_name": request.POST.get("from_name"), "self_company": request.POST.get("from_company"), @@ -102,7 +102,7 @@ def edit_invoice(request: HttpRequest, invoice_id): client_to_id = request.POST.get("selected_client") try: - client_to_obj = Client.objects.get(id=client_to_id, user=request.user) + client_to_obj = Client.objects.get(id=client_to_id, user=request.user) # type: ignore[misc] except (Client.DoesNotExist, ValueError): client_to_obj = None @@ -140,7 +140,7 @@ def edit_invoice(request: HttpRequest, invoice_id): messages.success(request, "Invoice edited") - if request.htmx: + if request.headers.get("Hx-Request", None): return render(request, "base/toasts.html") return invoice_edit_page_get(request, invoice_id) diff --git a/backend/views/core/settings/teams.py b/backend/views/core/settings/teams.py index ad7f90c5..b04f2b51 100644 --- a/backend/views/core/settings/teams.py +++ b/backend/views/core/settings/teams.py @@ -1,3 +1,5 @@ +from typing import Optional + from django.db.models import When, Case, BooleanField from django.http import HttpRequest from django.shortcuts import render @@ -6,8 +8,8 @@ def teams_dashboard(request: HttpRequest): - request.user: User = request.user - users_team: Optional[Team] = request.user.logged_in_as_team + request.user: User = request.user # type: ignore[misc] + users_team: Optional[Team] = request.user.logged_in_as_team # type: ignore[union-attr, assignment] if not users_team: user_with_counts = User.objects.prefetch_related("teams_joined", "teams_leader_of").get(pk=request.user.pk) From 1a838c1a5f5c2adcee7a5a709069ba268c8c59c6 Mon Sep 17 00:00:00 2001 From: Domejko Date: Sun, 14 Apr 2024 11:22:03 +0200 Subject: [PATCH 03/12] Mypm type errors fix --- backend/types/emails.py | 4 ++-- backend/views/core/invoices/edit.py | 2 +- settings/helpers.py | 9 ++------- settings/local_settings.py | 5 +---- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/backend/types/emails.py b/backend/types/emails.py index a7e5af17..9ca5b7e0 100644 --- a/backend/types/emails.py +++ b/backend/types/emails.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Literal, Optional, List, TypedDict, Dict +from typing import Literal, TypedDict from mypy_boto3_sesv2.type_defs import SendEmailResponseTypeDef, SendBulkEmailResponseTypeDef, BulkEmailEntryResultTypeDef @@ -14,7 +14,7 @@ class SingleEmailInput: destination: str | list[str] subject: str content: str | SingleTemplatedEmailContent - ConfigurationSetName: str + ConfigurationSetName: str | None = None from_address: str | None = None from_address_name_prefix: str | None = None diff --git a/backend/views/core/invoices/edit.py b/backend/views/core/invoices/edit.py index 78513703..97cb0064 100644 --- a/backend/views/core/invoices/edit.py +++ b/backend/views/core/invoices/edit.py @@ -140,7 +140,7 @@ def edit_invoice(request: HttpRequest, invoice_id): messages.success(request, "Invoice edited") - if request.headers.get("Hx-Request", None): + if request.htmx: # type: ignore[attr-defined] return render(request, "base/toasts.html") return invoice_edit_page_get(request, invoice_id) diff --git a/settings/helpers.py b/settings/helpers.py index 371c47da..2173167b 100644 --- a/settings/helpers.py +++ b/settings/helpers.py @@ -3,12 +3,7 @@ import json import os import sys -from dataclasses import dataclass from logging import exception -from typing import Literal, List, Any - -from collections.abc import Sequence -from typing import Optional import boto3 import environ @@ -105,7 +100,7 @@ def send_email(data: SingleEmailInput) -> SingleEmailSuccessResponse | SingleEma try: if isinstance(data.content, dict): - data_str: str | Any = ( + data_str = ( data.content.get("template_data") if isinstance(data.content.get("template_data"), str) else json.dumps(data.content.get("template_data")) @@ -128,7 +123,7 @@ def send_email(data: SingleEmailInput) -> SingleEmailSuccessResponse | SingleEma FromEmailAddress=from_email_address, Destination={"ToAddresses": data.destination}, Content={"Simple": {"Subject": {"Data": data.subject}, "Body": {"Text": {"Data": data.content}}}}, - ConfigurationSetName=data.ConfigurationSetName, + ConfigurationSetName=data.ConfigurationSetName or "", ) return SingleEmailSuccessResponse(response) except EMAIL_CLIENT.exceptions.MessageRejected: diff --git a/settings/local_settings.py b/settings/local_settings.py index 16697779..1081e1bb 100644 --- a/settings/local_settings.py +++ b/settings/local_settings.py @@ -1,5 +1,3 @@ -from typing import Any - import environ import os from pathlib import Path @@ -17,7 +15,7 @@ DB_TYPE = "mysql" if DB_TYPE in ["mysql", "mariadb"] else DB_TYPE if DB_TYPE == "mysql" or DB_TYPE == "postgres": - DATABASES: Any = { + DATABASES: dict = { "default": { "ENGINE": ( "django.db.backends.postgresql_psycopg2" @@ -47,7 +45,6 @@ } print("[BACKEND] Using sqlite3 database", flush=True) - ALLOWED_HOSTS: list[str | None] = ["localhost", "127.0.0.1"] os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = ( From 73e52c96bd04973eaca7c433751554927fd581db Mon Sep 17 00:00:00 2001 From: Domejko Date: Mon, 15 Apr 2024 16:13:39 +0200 Subject: [PATCH 04/12] Utils refactor. AuthenticatedHttpRequest subclass added. --- backend/utils/__init__.py | 0 backend/utils/feature_flags.py | 29 ++++++++++++++++++++++ backend/utils/http_utils.py | 23 +++++++++++++++++ backend/utils/quota_limit_ops.py | 42 ++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 backend/utils/__init__.py create mode 100644 backend/utils/feature_flags.py create mode 100644 backend/utils/http_utils.py create mode 100644 backend/utils/quota_limit_ops.py diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/utils/feature_flags.py b/backend/utils/feature_flags.py new file mode 100644 index 00000000..297ef1e5 --- /dev/null +++ b/backend/utils/feature_flags.py @@ -0,0 +1,29 @@ +from backend.models import FeatureFlags +from django.core.cache import cache +from django.core.cache.backends.redis import RedisCacheClient + +cache: RedisCacheClient = cache + + +def get_feature_status(feature, should_use_cache=True): + if should_use_cache: + key = f"myfinances:feature_flag:{feature}" + cached_value = cache.get(key) + if cached_value: + return cached_value + + value = FeatureFlags.objects.filter(name=feature).first() + if value: + if should_use_cache: + cache.set(key, value.value, timeout=300) + return value.value + else: + return False + + +def set_cache(key, value, timeout=300): + cache.set(key, value, timeout=timeout) + + +def get_cache(key): + return cache.get(key) diff --git a/backend/utils/http_utils.py b/backend/utils/http_utils.py new file mode 100644 index 00000000..4fbf63b2 --- /dev/null +++ b/backend/utils/http_utils.py @@ -0,0 +1,23 @@ +from django.http import HttpResponseRedirect +from django.http import HttpRequest + +from backend.models import User + + +class AuthenticatedHttpRequest(HttpRequest): + user: User + + +def redirect_to_last_visited(request, fallback_url="dashboard"): + """ + Redirects user to the last visited URL stored in session. + If no previous URL is found, redirects to the fallback URL. + :param request: HttpRequest object + :param fallback_url: URL to redirect to if no previous URL found + :return: HttpResponseRedirect object + """ + try: + last_visited_url = request.session.get("last_visited", fallback_url) + return HttpResponseRedirect(last_visited_url) + except KeyError: + return HttpResponseRedirect(fallback_url) diff --git a/backend/utils/quota_limit_ops.py b/backend/utils/quota_limit_ops.py new file mode 100644 index 00000000..e3a5b682 --- /dev/null +++ b/backend/utils/quota_limit_ops.py @@ -0,0 +1,42 @@ +from django.contrib import messages +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render +from django.urls import reverse + +from backend.models import QuotaLimit + + +def quota_usage_check_under( + request, limit: str | QuotaLimit, extra_data: str | int | None = None, api=False, htmx=False, add: int = 0 +) -> bool | HttpResponse | HttpResponseRedirect: + try: + quota_limit = QuotaLimit.objects.get(slug=limit) if isinstance(limit, str) else limit + except QuotaLimit.DoesNotExist: + return True + + if not quota_limit.strict_goes_above_limit(request.user, extra=extra_data, add=add): + return True + + if api and htmx: + messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") + return render(request, "partials/messages_list.html", {"autohide": False}) + elif api: + return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.name}'") + messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") + try: + last_visited_url = request.session["last_visited"] + current_url = request.build_absolute_uri() + if last_visited_url != current_url: + return HttpResponseRedirect(last_visited_url) + except KeyError: + pass + return HttpResponseRedirect(reverse("dashboard")) + + +def render_quota_error(request, quota_limit): + messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") + return render(request, "partials/messages_list.html", {"autohide": False}) + + +def render_quota_error_response(quota_limit): + return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.slug}'") From 8af757666bb4d25d2094720ab8d871d44969e166 Mon Sep 17 00:00:00 2001 From: Domejko Date: Mon, 15 Apr 2024 16:14:44 +0200 Subject: [PATCH 05/12] Mypy errors fix. Imports update --- backend/api/emails/send.py | 3 +- backend/api/invoices/schedule.py | 6 +- backend/api/quotas/requests.py | 2 +- backend/api/receipts/delete.py | 4 +- backend/api/receipts/fetch.py | 6 +- backend/api/settings/change_name.py | 7 +- backend/api/teams/kick.py | 4 +- backend/api/teams/leave.py | 16 ++-- backend/api/teams/switch_team.py | 41 +++++---- backend/decorators.py | 3 +- backend/templatetags/feature_enabled.py | 2 +- backend/utils.py | 90 ------------------- backend/views/core/auth/create_account.py | 2 +- backend/views/core/auth/passwords/generate.py | 15 ++-- backend/views/core/clients/create.py | 18 ++-- backend/views/core/invoices/create.py | 2 +- backend/views/core/invoices/schedule.py | 1 - infrastructure/aws/handler.py | 2 +- 18 files changed, 74 insertions(+), 150 deletions(-) delete mode 100644 backend/utils.py diff --git a/backend/api/emails/send.py b/backend/api/emails/send.py index 00dced4b..e8236c68 100644 --- a/backend/api/emails/send.py +++ b/backend/api/emails/send.py @@ -2,7 +2,6 @@ import re from dataclasses import dataclass -from typing import List, Tuple from collections.abc import Iterator @@ -29,7 +28,7 @@ BulkEmailErrorResponse, BulkTemplatedEmailInput, ) -from backend.utils import quota_usage_check_under +from backend.utils.quota_limit_ops import quota_usage_check_under from settings.helpers import send_email, send_templated_bulk_email diff --git a/backend/api/invoices/schedule.py b/backend/api/invoices/schedule.py index de9daedc..c272e967 100644 --- a/backend/api/invoices/schedule.py +++ b/backend/api/invoices/schedule.py @@ -1,5 +1,3 @@ -import json - from django.contrib import messages from django.db.models import Q from django.http import HttpResponse, HttpResponseForbidden @@ -11,11 +9,11 @@ from django_ratelimit.core import is_ratelimited from mypy_boto3_iam.type_defs import GetRoleResponseTypeDef -from backend.decorators import feature_flag_check, quota_usage_check +from backend.decorators import feature_flag_check from backend.models import Invoice, AuditLog, APIKey, InvoiceOnetimeSchedule, InvoiceURL, QuotaUsage from backend.types.emails import SingleEmailInput from backend.types.htmx import HtmxHttpRequest -from backend.utils import quota_usage_check_under +from backend.utils.quota_limit_ops import quota_usage_check_under from infrastructure.aws.handler import get_iam_client from infrastructure.aws.schedules.create_schedule import ( create_onetime_schedule, diff --git a/backend/api/quotas/requests.py b/backend/api/quotas/requests.py index dde63413..ec2c6cd1 100644 --- a/backend/api/quotas/requests.py +++ b/backend/api/quotas/requests.py @@ -8,7 +8,7 @@ from backend.decorators import superuser_only from backend.models import QuotaIncreaseRequest, QuotaLimit, QuotaUsage, QuotaOverrides -from backend.utils import quota_usage_check_under +from backend.utils.quota_limit_ops import quota_usage_check_under def submit_request(request: HttpRequest, slug) -> HttpResponse: diff --git a/backend/api/receipts/delete.py b/backend/api/receipts/delete.py index c966a12d..aea6682d 100644 --- a/backend/api/receipts/delete.py +++ b/backend/api/receipts/delete.py @@ -4,12 +4,14 @@ from django.shortcuts import render, redirect from django.urls import resolve, Resolver404, reverse from django.views.decorators.http import require_http_methods + from backend.models import Receipt +from backend.utils.http_utils import AuthenticatedHttpRequest @require_http_methods(["DELETE"]) @login_required -def receipt_delete(request: HttpRequest, id: int): +def receipt_delete(request: AuthenticatedHttpRequest, id: int): try: receipt = Receipt.objects.get(id=id) except Receipt.DoesNotExist: diff --git a/backend/api/receipts/fetch.py b/backend/api/receipts/fetch.py index 59fd9e22..483a342d 100644 --- a/backend/api/receipts/fetch.py +++ b/backend/api/receipts/fetch.py @@ -2,18 +2,18 @@ from django.http import HttpRequest from django.shortcuts import render, redirect -from backend.models import Receipt +from backend.models import Receipt, User def fetch_all_receipts(request: HttpRequest): context = {} - if not request.htmx: + if not request.htmx: # type: ignore[attr-defined] return redirect("receipts dashboard") search_text = request.GET.get("search") results = Receipt.objects.order_by("-date") - if request.user.logged_in_as_team: + if isinstance(request.user, User) and request.user.logged_in_as_team: results = results.filter(organization=request.user.logged_in_as_team) else: results = results.filter(user=request.user) diff --git a/backend/api/settings/change_name.py b/backend/api/settings/change_name.py index 32f1324b..9d4d3e71 100644 --- a/backend/api/settings/change_name.py +++ b/backend/api/settings/change_name.py @@ -1,4 +1,5 @@ from django.contrib import messages +from django.contrib.auth.models import AnonymousUser from django.http import HttpRequest, HttpResponse from django.shortcuts import render from django.views.decorators.http import require_http_methods @@ -6,7 +7,7 @@ @require_http_methods(["POST"]) def change_account_name(request: HttpRequest): - if not request.htmx: + if not request.htmx: # type: ignore[attr-defined] return HttpResponse("Invalid Request", status=405) htmx_return = "base/toasts.html" @@ -18,6 +19,10 @@ def change_account_name(request: HttpRequest): messages.error(request, "Please enter a valid firstname or lastname.") return render(request, htmx_return) + if isinstance(request.user, AnonymousUser): + messages.error(request, "Please log in to change your account name.") + return render(request, htmx_return) + if request.user.first_name == first_name and request.user.last_name == last_name: messages.warning(request, "You already have this name.") return render(request, htmx_return) diff --git a/backend/api/teams/kick.py b/backend/api/teams/kick.py index f406e094..47255ab7 100644 --- a/backend/api/teams/kick.py +++ b/backend/api/teams/kick.py @@ -6,7 +6,7 @@ def kick_user(request: HttpRequest, user_id): - user: User = User.objects.filter(id=user_id).first() + user: User | None = User.objects.filter(id=user_id).first() confirmation_text = request.POST.get("confirmation_text") if not user: messages.error(request, "User not found") @@ -16,7 +16,7 @@ def kick_user(request: HttpRequest, user_id): messages.error(request, "Invalid confirmation") return redirect("user settings teams") - team: Team = user.teams_joined.first() + team: Team | None = user.teams_joined.first() if not team: messages.error(request, "User is not apart of your team") return redirect("user settings teams") diff --git a/backend/api/teams/leave.py b/backend/api/teams/leave.py index c1920806..357e60be 100644 --- a/backend/api/teams/leave.py +++ b/backend/api/teams/leave.py @@ -13,7 +13,7 @@ def return_error_notif(request: HttpRequest, message: str): def leave_team_confirmed(request: HttpRequest, team_id): - team: Team = Team.objects.filter(id=team_id).first() + team: Team | None = Team.objects.filter(id=team_id).first() if not team: return return_error_notif(request, "Team not found") @@ -21,11 +21,11 @@ def leave_team_confirmed(request: HttpRequest, team_id): if team.leader == request.user: # may be changed in the future. If no members allow delete return return_error_notif(request, "You cannot leave your own team") - if not request.user.teams_joined.filter(id=team_id).exists(): + if isinstance(request.user, User) and request.user.teams_joined.filter(id=team_id).exists(): + team.members.remove(request.user) + messages.success(request, f"You have successfully left the team {team.name}") + response = HttpResponse(status=200) + response["HX-Refresh"] = "true" + return response + else: return return_error_notif(request, "You are not a member of this team") - - team.members.remove(request.user) - messages.success(request, f"You have successfully left the team {team.name}") - response = HttpResponse(status=200) - response["HX-Refresh"] = "true" - return response diff --git a/backend/api/teams/switch_team.py b/backend/api/teams/switch_team.py index 9c8508ed..90b3db68 100644 --- a/backend/api/teams/switch_team.py +++ b/backend/api/teams/switch_team.py @@ -1,4 +1,5 @@ from django.contrib import messages +from django.contrib.auth.models import AnonymousUser from django.http import HttpRequest, HttpResponse from django.shortcuts import render @@ -7,32 +8,36 @@ def switch_team(request: HttpRequest, team_id): if not team_id: - if not request.user.logged_in_as_team: + if not hasattr(request.user, "logged_in_as_team"): messages.error(request, "You are not logged into an organization") - request.user.logged_in_as_team = None - request.user.save() - messages.success(request, "You are now logged into your personal account") - response = HttpResponse(status=200) - response["HX-Refresh"] = "true" - return response - team: Team = Team.objects.filter(id=team_id).first() + if not isinstance(request.user, AnonymousUser): + request.user.logged_in_as_team = None + request.user.save() + messages.success(request, "You are now logged into your personal account") + response = HttpResponse(status=200) + response["HX-Refresh"] = "true" + return response + + team: Team | None = Team.objects.filter(id=team_id).first() if not team: messages.error(request, "Team not found") return render(request, "partials/messages_list.html") - if request.user.logged_in_as_team == team: - messages.error(request, "You are already logged in for this team") - return render(request, "partials/messages_list.html") + if not isinstance(request.user, AnonymousUser): + if request.user.logged_in_as_team == team: + messages.error(request, "You are already logged in for this team") + return render(request, "partials/messages_list.html") - if not request.user.teams_leader_of.filter(id=team_id).exists() and not request.user.teams_joined.filter(id=team_id).exists(): - messages.error(request, "You are not a member of this team") - return render(request, "partials/messages_list.html") + if not request.user.teams_leader_of.filter(id=team_id).exists() and not request.user.teams_joined.filter(id=team_id).exists(): + messages.error(request, "You are not a member of this team") + return render(request, "partials/messages_list.html") + + messages.success(request, f"Now signing in for {team.name}") + request.user.logged_in_as_team = team + request.user.save() - messages.success(request, f"Now signing in for {team.name}") - request.user.logged_in_as_team = team - request.user.save() response = HttpResponse(status=200) response["HX-Refresh"] = "true" return response @@ -40,7 +45,7 @@ def switch_team(request: HttpRequest, team_id): def get_dropdown(request: HttpRequest): - if not request.htmx: + if not request.htmx: # type: ignore[attr-defined] return HttpResponse("Invalid Request", status=405) return render(request, "base/topbar/_organizations_list.html") diff --git a/backend/decorators.py b/backend/decorators.py index 9aff133c..94ef2a01 100644 --- a/backend/decorators.py +++ b/backend/decorators.py @@ -1,7 +1,6 @@ from __future__ import annotations from functools import wraps -from typing import Optional from django.contrib import messages from django.http import HttpResponse @@ -11,7 +10,7 @@ from django.urls import reverse from backend.models import QuotaLimit -from backend.utils import get_feature_status +from backend.utils.feature_flags import get_feature_status def not_authenticated(view_func): diff --git a/backend/templatetags/feature_enabled.py b/backend/templatetags/feature_enabled.py index d99bef57..17043ebe 100644 --- a/backend/templatetags/feature_enabled.py +++ b/backend/templatetags/feature_enabled.py @@ -1,6 +1,6 @@ from django import template -from backend.utils import get_feature_status +from backend.utils.feature_flags import get_feature_status register = template.Library() diff --git a/backend/utils.py b/backend/utils.py deleted file mode 100644 index 4112c9e9..00000000 --- a/backend/utils.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -from typing import Optional - -from django.contrib import messages -from django.core.cache import cache -from django.core.cache.backends.redis import RedisCacheClient -from django.http import HttpResponse -from django.http import HttpResponseRedirect -from django.shortcuts import render -from django.urls import reverse - -cache: RedisCacheClient = cache - -from backend.models import FeatureFlags, QuotaLimit - - -def get_feature_status(feature, should_use_cache=True): - if should_use_cache: - key = f"myfinances:feature_flag:{feature}" - cached_value = cache.get(key) - if cached_value: - return cached_value - - value = FeatureFlags.objects.filter(name=feature).first() - if value: - if should_use_cache: - cache.set(key, value.value, timeout=300) - return value.value - else: - return False - - -def quota_usage_check_under( - request, limit: str | QuotaLimit, extra_data: str | int | None = None, api=False, htmx=False, add: int = 0 -) -> bool | HttpResponse | HttpResponseRedirect: - try: - quota_limit = QuotaLimit.objects.get(slug=limit) if isinstance(limit, str) else limit - except QuotaLimit.DoesNotExist: - return True - - if not quota_limit.strict_goes_above_limit(request.user, extra=extra_data, add=add): - return True - - if api and htmx: - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") - return render(request, "partials/messages_list.html", {"autohide": False}) - elif api: - return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.name}'") - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") - try: - last_visited_url = request.session["last_visited"] - current_url = request.build_absolute_uri() - if last_visited_url != current_url: - return HttpResponseRedirect(last_visited_url) - except KeyError: - pass - return HttpResponseRedirect(reverse("dashboard")) - - -def set_cache(key, value, timeout=300): - cache.set(key, value, timeout=timeout) - - -def get_cache(key): - return cache.get(key) - - -def render_quota_error(request, quota_limit): - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") - return render(request, "partials/messages_list.html", {"autohide": False}) - - -def render_quota_error_response(quota_limit): - return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.slug}'") - - -def redirect_to_last_visited(request, fallback_url="dashboard"): - """ - Redirects user to the last visited URL stored in session. - If no previous URL is found, redirects to the fallback URL. - :param request: HttpRequest object - :param fallback_url: URL to redirect to if no previous URL found - :return: HttpResponseRedirect object - """ - try: - last_visited_url = request.session.get("last_visited", fallback_url) - return HttpResponseRedirect(last_visited_url) - except KeyError: - return HttpResponseRedirect(fallback_url) diff --git a/backend/views/core/auth/create_account.py b/backend/views/core/auth/create_account.py index b61f1497..3ae9ca9e 100644 --- a/backend/views/core/auth/create_account.py +++ b/backend/views/core/auth/create_account.py @@ -7,7 +7,7 @@ from django.views import View from backend.models import User -from backend.utils import get_feature_status +from backend.utils.feature_flags import get_feature_status from settings.settings import ( SOCIAL_AUTH_GITHUB_ENABLED, SOCIAL_AUTH_GOOGLE_OAUTH2_ENABLED, diff --git a/backend/views/core/auth/passwords/generate.py b/backend/views/core/auth/passwords/generate.py index 912eb7e8..259b3ddb 100644 --- a/backend/views/core/auth/passwords/generate.py +++ b/backend/views/core/auth/passwords/generate.py @@ -1,11 +1,12 @@ from datetime import datetime, timedelta, date +from django.contrib import messages from django.contrib.auth.hashers import make_password from django.core.exceptions import ValidationError from django.core.validators import validate_email from django.http import HttpRequest from django.shortcuts import redirect -from django.urls import reverse, resolve +from django.urls import reverse, resolve, NoReverseMatch from django.utils import timezone from backend.models import * @@ -25,20 +26,24 @@ def set_password_generate(request: HttpRequest): USER = request.GET.get("id") NEXT = request.GET.get("next") or "index" + if USER is None: + messages.error(request, "User ID is missing") + return redirect("dashboard") + if not USER.isnumeric(): messages.error(request, "User ID must be a valid integer") return redirect("dashboard") - USER = User.objects.filter(id=USER).first() + USER_OBJ = User.objects.filter(id=USER).first() - if not USER: + if not USER_OBJ: messages.error(request, f"User not found") return redirect("dashboard") CODE = RandomCode(40) HASHED_CODE = make_password(CODE, salt=settings.SECRET_KEY) PWD_SECRET, created = PasswordSecret.objects.update_or_create( - user=USER, + user=USER_OBJ, defaults={"expires": date.today() + timedelta(days=3), "secret": HASHED_CODE}, ) PWD_SECRET.save() @@ -50,7 +55,7 @@ def set_password_generate(request: HttpRequest): try: resolve(NEXT) return redirect(NEXT) - except resolve.NoReverseMatch: + except NoReverseMatch: return redirect("dashboard") diff --git a/backend/views/core/clients/create.py b/backend/views/core/clients/create.py index 004cfc33..38574fb4 100644 --- a/backend/views/core/clients/create.py +++ b/backend/views/core/clients/create.py @@ -1,4 +1,5 @@ from django.contrib import messages +from django.contrib.auth.models import AnonymousUser from django.http import HttpRequest from django.shortcuts import render, redirect @@ -24,14 +25,15 @@ def create_client(request: HttpRequest): messages.error(request, error) return redirect("clients create") - if request.user.logged_in_as_team: - client = Client.objects.create( - organization=request.user.logged_in_as_team, - ) - else: - client = Client.objects.create( - user=request.user, - ) + if not isinstance(request.user, AnonymousUser): + if request.user.logged_in_as_team: + client = Client.objects.create( + organization=request.user.logged_in_as_team, + ) + else: + client = Client.objects.create( + user=request.user, + ) for model_field, new_value in client_details.items(): setattr(client, model_field, new_value) diff --git a/backend/views/core/invoices/create.py b/backend/views/core/invoices/create.py index 419503eb..545a7b8b 100644 --- a/backend/views/core/invoices/create.py +++ b/backend/views/core/invoices/create.py @@ -7,7 +7,7 @@ from backend.decorators import quota_usage_check from backend.models import Invoice, InvoiceItem, Client, InvoiceProduct, QuotaUsage -from backend.utils import quota_usage_check_under +from backend.utils.quota_limit_ops import quota_usage_check_under def invoice_page_get(request: HttpRequest): diff --git a/backend/views/core/invoices/schedule.py b/backend/views/core/invoices/schedule.py index 67d23c16..da82f2ce 100644 --- a/backend/views/core/invoices/schedule.py +++ b/backend/views/core/invoices/schedule.py @@ -4,7 +4,6 @@ from backend.decorators import feature_flag_check from backend.models import Invoice, QuotaLimit -from backend.utils import quota_usage_check_under @feature_flag_check("isInvoiceSchedulingEnabled", True) diff --git a/infrastructure/aws/handler.py b/infrastructure/aws/handler.py index 7f657950..30705d99 100644 --- a/infrastructure/aws/handler.py +++ b/infrastructure/aws/handler.py @@ -11,7 +11,7 @@ from mypy_boto3_stepfunctions.client import SFNClient from backend.models import FeatureFlags -from backend.utils import get_feature_status +from backend.utils.feature_flags import get_feature_status from settings.helpers import get_var from settings.settings import AWS_TAGS_APP_NAME From 03bf0e8dc9f596ea1f0f8ed7af0abfec64943b18 Mon Sep 17 00:00:00 2001 From: Domejko Date: Sat, 20 Apr 2024 11:08:23 +0200 Subject: [PATCH 06/12] My py error fix --- backend/utils.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 backend/utils.py diff --git a/backend/utils.py b/backend/utils.py new file mode 100644 index 00000000..1528a993 --- /dev/null +++ b/backend/utils.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from typing import Optional + +from django.contrib import messages +from django.core.cache import cache +from django.core.cache.backends.redis import RedisCacheClient +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.shortcuts import render +from django.urls import reverse + +cache: RedisCacheClient = cache + +from backend.models import FeatureFlags, QuotaLimit + + +def get_feature_status(feature, should_use_cache=True): + if should_use_cache: + key = f"myfinances:feature_flag:{feature}" + cached_value = cache.get(key) + if cached_value: + return cached_value + + value = FeatureFlags.objects.filter(name=feature).first() + if value: + if should_use_cache: + cache.set(key, value.value, timeout=300) + return value.value + else: + return False + + +def quota_usage_check_under( + request, limit: str | QuotaLimit, extra_data: str | int | None = None, api=False, htmx=False, add: int = 0 +) -> bool | HttpResponse | HttpResponseRedirect: + try: + quota_limit = QuotaLimit.objects.get(slug=limit) if isinstance(limit, str) else limit + except QuotaLimit.DoesNotExist: + return True + + if not quota_limit.strict_goes_above_limit(request.user, extra=extra_data, add=add): + return True + + if api and htmx: + messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") + return render(request, "base/toast.html", {"autohide": False}) + elif api: + return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.name}'") + messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") + try: + last_visited_url = request.session["last_visited"] + current_url = request.build_absolute_uri() + if last_visited_url != current_url: + return HttpResponseRedirect(last_visited_url) + except KeyError: + pass + return HttpResponseRedirect(reverse("dashboard")) + + +def set_cache(key, value, timeout=300): + cache.set(key, value, timeout=timeout) + + +def get_cache(key): + return cache.get(key) + + +def render_quota_error(request, quota_limit): + messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") + return render(request, "partials/messages_list.html", {"autohide": False}) + + +def render_quota_error_response(quota_limit): + return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.slug}'") + + +def redirect_to_last_visited(request, fallback_url="dashboard"): + """ + Redirects user to the last visited URL stored in session. + If no previous URL is found, redirects to the fallback URL. + :param request: HttpRequest object + :param fallback_url: URL to redirect to if no previous URL found + :return: HttpResponseRedirect object + """ + try: + last_visited_url = request.session.get("last_visited", fallback_url) + return HttpResponseRedirect(last_visited_url) + except KeyError: + return HttpResponseRedirect(fallback_url) From 9ffb45775d9fe3b272a9a20f8a7b494e0ad21d3c Mon Sep 17 00:00:00 2001 From: Domejko Date: Sat, 20 Apr 2024 11:33:20 +0200 Subject: [PATCH 07/12] Moved event_bridhe_scheduler inside fuction --- backend/utils.py | 90 ------------------- .../aws/schedules/delete_reminder.py | 3 +- .../aws/schedules/delete_schedule.py | 3 +- 3 files changed, 2 insertions(+), 94 deletions(-) delete mode 100644 backend/utils.py diff --git a/backend/utils.py b/backend/utils.py deleted file mode 100644 index 1528a993..00000000 --- a/backend/utils.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -from typing import Optional - -from django.contrib import messages -from django.core.cache import cache -from django.core.cache.backends.redis import RedisCacheClient -from django.http import HttpResponse -from django.http import HttpResponseRedirect -from django.shortcuts import render -from django.urls import reverse - -cache: RedisCacheClient = cache - -from backend.models import FeatureFlags, QuotaLimit - - -def get_feature_status(feature, should_use_cache=True): - if should_use_cache: - key = f"myfinances:feature_flag:{feature}" - cached_value = cache.get(key) - if cached_value: - return cached_value - - value = FeatureFlags.objects.filter(name=feature).first() - if value: - if should_use_cache: - cache.set(key, value.value, timeout=300) - return value.value - else: - return False - - -def quota_usage_check_under( - request, limit: str | QuotaLimit, extra_data: str | int | None = None, api=False, htmx=False, add: int = 0 -) -> bool | HttpResponse | HttpResponseRedirect: - try: - quota_limit = QuotaLimit.objects.get(slug=limit) if isinstance(limit, str) else limit - except QuotaLimit.DoesNotExist: - return True - - if not quota_limit.strict_goes_above_limit(request.user, extra=extra_data, add=add): - return True - - if api and htmx: - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") - return render(request, "base/toast.html", {"autohide": False}) - elif api: - return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.name}'") - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") - try: - last_visited_url = request.session["last_visited"] - current_url = request.build_absolute_uri() - if last_visited_url != current_url: - return HttpResponseRedirect(last_visited_url) - except KeyError: - pass - return HttpResponseRedirect(reverse("dashboard")) - - -def set_cache(key, value, timeout=300): - cache.set(key, value, timeout=timeout) - - -def get_cache(key): - return cache.get(key) - - -def render_quota_error(request, quota_limit): - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") - return render(request, "partials/messages_list.html", {"autohide": False}) - - -def render_quota_error_response(quota_limit): - return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.slug}'") - - -def redirect_to_last_visited(request, fallback_url="dashboard"): - """ - Redirects user to the last visited URL stored in session. - If no previous URL is found, redirects to the fallback URL. - :param request: HttpRequest object - :param fallback_url: URL to redirect to if no previous URL found - :return: HttpResponseRedirect object - """ - try: - last_visited_url = request.session.get("last_visited", fallback_url) - return HttpResponseRedirect(last_visited_url) - except KeyError: - return HttpResponseRedirect(fallback_url) diff --git a/infrastructure/aws/schedules/delete_reminder.py b/infrastructure/aws/schedules/delete_reminder.py index e09d5c36..0ad38303 100644 --- a/infrastructure/aws/schedules/delete_reminder.py +++ b/infrastructure/aws/schedules/delete_reminder.py @@ -4,8 +4,6 @@ from infrastructure.aws.handler import get_event_bridge_scheduler from settings.settings import AWS_TAGS_APP_NAME -event_bridge_scheduler = get_event_bridge_scheduler() - @dataclass(frozen=True) class SuccessResponse: @@ -23,6 +21,7 @@ class ErrorResponse: def delete_reminder(invoice_id, reminder_id) -> DeleteScheduleResponse: + event_bridge_scheduler = get_event_bridge_scheduler() try: print(f"[AWS] [SCHEDULE] Deleting schedule: {invoice_id}-{reminder_id}") event_bridge_scheduler.delete_schedule( diff --git a/infrastructure/aws/schedules/delete_schedule.py b/infrastructure/aws/schedules/delete_schedule.py index 715f536d..fdc36e02 100644 --- a/infrastructure/aws/schedules/delete_schedule.py +++ b/infrastructure/aws/schedules/delete_schedule.py @@ -3,8 +3,6 @@ from infrastructure.aws.handler import get_event_bridge_scheduler from settings.settings import AWS_TAGS_APP_NAME -event_bridge_scheduler = get_event_bridge_scheduler() - @dataclass(frozen=True) class SuccessResponse: @@ -20,6 +18,7 @@ class ErrorResponse: def delete_schedule(invoice_id, schedule_id) -> DeleteScheduleResponse: + event_bridge_scheduler = get_event_bridge_scheduler() try: print(f"[AWS] [SCHEDULE] Deleting schedule: {invoice_id}-{schedule_id}") event_bridge_scheduler.delete_schedule( From c15075819c99affb3009dd72df22f1bcc55390c3 Mon Sep 17 00:00:00 2001 From: Domejko Date: Sat, 20 Apr 2024 11:34:51 +0200 Subject: [PATCH 08/12] Updated imports --- backend/api/base/modal.py | 2 +- backend/api/invoices/reminders/create.py | 2 +- backend/decorators.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/api/base/modal.py b/backend/api/base/modal.py index 667d1e92..29a67110 100644 --- a/backend/api/base/modal.py +++ b/backend/api/base/modal.py @@ -10,7 +10,7 @@ from backend.models import QuotaLimit from backend.models import Team from backend.models import UserSettings -from backend.utils import quota_usage_check_under +from backend.utils.quota_limit_ops import quota_usage_check_under # Still working on diff --git a/backend/api/invoices/reminders/create.py b/backend/api/invoices/reminders/create.py index 8aeef45d..07b1538c 100644 --- a/backend/api/invoices/reminders/create.py +++ b/backend/api/invoices/reminders/create.py @@ -5,7 +5,7 @@ from django.utils import timezone from backend.models import Invoice, InvoiceReminder, QuotaUsage -from backend.utils import quota_usage_check_under +from backend.utils.quota_limit_ops import quota_usage_check_under from infrastructure.aws.schedules.create_reminder import CreateReminderInputData, create_reminder_schedule from backend.types.htmx import HtmxHttpRequest diff --git a/backend/decorators.py b/backend/decorators.py index dfebde94..04183211 100644 --- a/backend/decorators.py +++ b/backend/decorators.py @@ -12,7 +12,7 @@ from django.urls import reverse from backend.models import QuotaLimit -from backend.utils import get_feature_status +from backend.utils.feature_flags import get_feature_status def not_authenticated(view_func): From 8892644567f717d131c5f2de8df14d4b1b222bdd Mon Sep 17 00:00:00 2001 From: Domejko Date: Sat, 20 Apr 2024 15:24:04 +0200 Subject: [PATCH 09/12] Auth User moved to HtmxHttpRequest --- backend/types/htmx.py | 8 ++++++++ backend/utils/http_utils.py | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/types/htmx.py b/backend/types/htmx.py index 178c49db..9ec6435a 100644 --- a/backend/types/htmx.py +++ b/backend/types/htmx.py @@ -1,6 +1,14 @@ +from django.contrib.auth.models import AnonymousUser from django.http import HttpRequest from django_htmx.middleware import HtmxDetails +from backend.models import User + class HtmxHttpRequest(HttpRequest): htmx: HtmxDetails + user: User + + +class UnauthorizedHttpRequest(HttpRequest): + user: AnonymousUser diff --git a/backend/utils/http_utils.py b/backend/utils/http_utils.py index 4fbf63b2..95446893 100644 --- a/backend/utils/http_utils.py +++ b/backend/utils/http_utils.py @@ -1,11 +1,4 @@ from django.http import HttpResponseRedirect -from django.http import HttpRequest - -from backend.models import User - - -class AuthenticatedHttpRequest(HttpRequest): - user: User def redirect_to_last_visited(request, fallback_url="dashboard"): From 3e3933bd899b15e9b2271330b9e54f3972d570dd Mon Sep 17 00:00:00 2001 From: Domejko Date: Sat, 20 Apr 2024 15:25:45 +0200 Subject: [PATCH 10/12] HttpRequest replaced with authorized HtmxHttpRequest --- backend/api/receipts/delete.py | 4 ++-- backend/views/core/clients/create.py | 21 +++++++++---------- backend/views/core/clients/dashboard.py | 5 +++-- .../core/currency_converter/dashboard.py | 3 ++- backend/views/core/emails/dashboard.py | 5 +++-- backend/views/core/invoices/create.py | 8 +++---- backend/views/core/invoices/dashboard.py | 5 +++-- backend/views/core/invoices/edit.py | 11 +++++----- backend/views/core/invoices/manage_access.py | 9 ++++---- backend/views/core/invoices/overview.py | 7 +++---- backend/views/core/invoices/schedule.py | 5 +++-- backend/views/core/quotas/view.py | 9 ++++---- backend/views/core/receipts/dashboard.py | 5 +++-- backend/views/core/settings/teams.py | 6 +++--- 14 files changed, 55 insertions(+), 48 deletions(-) diff --git a/backend/api/receipts/delete.py b/backend/api/receipts/delete.py index aea6682d..a3e297e9 100644 --- a/backend/api/receipts/delete.py +++ b/backend/api/receipts/delete.py @@ -6,12 +6,12 @@ from django.views.decorators.http import require_http_methods from backend.models import Receipt -from backend.utils.http_utils import AuthenticatedHttpRequest +from backend.types.htmx import HtmxHttpRequest @require_http_methods(["DELETE"]) @login_required -def receipt_delete(request: AuthenticatedHttpRequest, id: int): +def receipt_delete(request: HtmxHttpRequest, id: int): try: receipt = Receipt.objects.get(id=id) except Receipt.DoesNotExist: diff --git a/backend/views/core/clients/create.py b/backend/views/core/clients/create.py index 38574fb4..f78c5e96 100644 --- a/backend/views/core/clients/create.py +++ b/backend/views/core/clients/create.py @@ -1,12 +1,12 @@ from django.contrib import messages from django.contrib.auth.models import AnonymousUser -from django.http import HttpRequest from django.shortcuts import render, redirect from backend.models import Client +from backend.types.htmx import HtmxHttpRequest -def create_client(request: HttpRequest): +def create_client(request: HtmxHttpRequest): if request.method == "GET": return render(request, "pages/clients/create/create.html") @@ -25,15 +25,14 @@ def create_client(request: HttpRequest): messages.error(request, error) return redirect("clients create") - if not isinstance(request.user, AnonymousUser): - if request.user.logged_in_as_team: - client = Client.objects.create( - organization=request.user.logged_in_as_team, - ) - else: - client = Client.objects.create( - user=request.user, - ) + if request.user.logged_in_as_team: + client = Client.objects.create( + organization=request.user.logged_in_as_team, + ) + else: + client = Client.objects.create( + user=request.user, + ) for model_field, new_value in client_details.items(): setattr(client, model_field, new_value) diff --git a/backend/views/core/clients/dashboard.py b/backend/views/core/clients/dashboard.py index ca10c4d1..eed5cc43 100644 --- a/backend/views/core/clients/dashboard.py +++ b/backend/views/core/clients/dashboard.py @@ -1,6 +1,7 @@ -from django.http import HttpRequest from django.shortcuts import render +from backend.types.htmx import HtmxHttpRequest -def clients_dashboard(request: HttpRequest): + +def clients_dashboard(request: HtmxHttpRequest): return render(request, "pages/clients/dashboard/dashboard.html") diff --git a/backend/views/core/currency_converter/dashboard.py b/backend/views/core/currency_converter/dashboard.py index 1846bd42..cb1c91cf 100644 --- a/backend/views/core/currency_converter/dashboard.py +++ b/backend/views/core/currency_converter/dashboard.py @@ -3,9 +3,10 @@ from forex_python.converter import CurrencyRates from backend.models import * +from backend.types.htmx import HtmxHttpRequest -def currency_convert_view(request: HttpRequest): +def currency_convert_view(request: HtmxHttpRequest): context = {} usersettings, created = UserSettings.objects.get_or_create(user=request.user) diff --git a/backend/views/core/emails/dashboard.py b/backend/views/core/emails/dashboard.py index aa25593b..862ab262 100644 --- a/backend/views/core/emails/dashboard.py +++ b/backend/views/core/emails/dashboard.py @@ -1,9 +1,10 @@ from __future__ import annotations -from django.http import HttpRequest from django.http import HttpResponse from django.shortcuts import render +from backend.types.htmx import HtmxHttpRequest -def dashboard(request: HttpRequest) -> HttpResponse: + +def dashboard(request: HtmxHttpRequest) -> HttpResponse: return render(request, "pages/emails/dashboard.html", {}) diff --git a/backend/views/core/invoices/create.py b/backend/views/core/invoices/create.py index 545a7b8b..c2870307 100644 --- a/backend/views/core/invoices/create.py +++ b/backend/views/core/invoices/create.py @@ -1,16 +1,16 @@ from datetime import datetime from django.contrib import messages -from django.http import HttpRequest from django.shortcuts import render, redirect from django.views.decorators.http import require_http_methods from backend.decorators import quota_usage_check from backend.models import Invoice, InvoiceItem, Client, InvoiceProduct, QuotaUsage from backend.utils.quota_limit_ops import quota_usage_check_under +from backend.types.htmx import HtmxHttpRequest -def invoice_page_get(request: HttpRequest): +def invoice_page_get(request: HtmxHttpRequest): check_usage = quota_usage_check_under(request, "invoices-count") if not isinstance(check_usage, bool): return check_usage @@ -22,7 +22,7 @@ def invoice_page_get(request: HttpRequest): @quota_usage_check("invoices-count") -def invoice_page_post(request: HttpRequest): +def invoice_page_post(request: HtmxHttpRequest): invoice_items = [ InvoiceItem.objects.create(name=row[0], description=row[1], hours=row[2], price_per_hour=row[3]) for row in zip( @@ -92,7 +92,7 @@ def invoice_page_post(request: HttpRequest): @require_http_methods(["GET", "POST"]) -def create_invoice_page(request: HttpRequest): +def create_invoice_page(request: HtmxHttpRequest): if request.method == "POST": return invoice_page_post(request) return invoice_page_get(request) diff --git a/backend/views/core/invoices/dashboard.py b/backend/views/core/invoices/dashboard.py index 9f76250a..013cfd24 100644 --- a/backend/views/core/invoices/dashboard.py +++ b/backend/views/core/invoices/dashboard.py @@ -3,15 +3,16 @@ from backend.decorators import * from backend.models import * +from backend.types.htmx import HtmxHttpRequest -def invoices_dashboard(request: HttpRequest): +def invoices_dashboard(request: HtmxHttpRequest): context = {} return render(request, "pages/invoices/dashboard/dashboard.html", context) -def invoices_dashboard_id(request: HttpRequest, invoice_id): +def invoices_dashboard_id(request: HtmxHttpRequest, invoice_id): if invoice_id == "create": return redirect("invoices:create") elif type(invoice_id) != "int": diff --git a/backend/views/core/invoices/edit.py b/backend/views/core/invoices/edit.py index 97cb0064..6ba8cab3 100644 --- a/backend/views/core/invoices/edit.py +++ b/backend/views/core/invoices/edit.py @@ -1,11 +1,12 @@ from datetime import datetime from django.contrib import messages -from django.http import HttpRequest, JsonResponse +from django.http import JsonResponse from django.shortcuts import render from django.views.decorators.http import require_http_methods from backend.models import Invoice, Client, InvoiceItem +from backend.types.htmx import HtmxHttpRequest # RELATED PATH FILES : \frontend\templates\pages\invoices\dashboard\_fetch_body.html, \backend\urls.py @@ -65,13 +66,13 @@ def invoice_edit_page_get(request, invoice_id): # when user changes/modifies any of the fields with new information (during edit invoice) @require_http_methods(["POST"]) -def edit_invoice(request: HttpRequest, invoice_id): +def edit_invoice(request: HtmxHttpRequest, invoice_id): try: invoice = Invoice.objects.get(id=invoice_id) except Invoice.DoesNotExist: return JsonResponse({"message": "Invoice not found"}, status=404) - if request.user.logged_in_as_team and request.user.logged_in_as_team != invoice.organization: # type: ignore[union-attr] + if request.user.logged_in_as_team and request.user.logged_in_as_team != invoice.organization: return JsonResponse( {"message": "You do not have permission to edit this invoice"}, status=403, @@ -140,7 +141,7 @@ def edit_invoice(request: HttpRequest, invoice_id): messages.success(request, "Invoice edited") - if request.htmx: # type: ignore[attr-defined] + if request.htmx: return render(request, "base/toasts.html") return invoice_edit_page_get(request, invoice_id) @@ -148,7 +149,7 @@ def edit_invoice(request: HttpRequest, invoice_id): # decorator & view function for rendering page and updating invoice items in the backend @require_http_methods(["GET", "POST"]) -def edit_invoice_page(request: HttpRequest, invoice_id): +def edit_invoice_page(request: HtmxHttpRequest, invoice_id): if request.method == "POST": return edit_invoice(request, invoice_id) return invoice_edit_page_get(request, invoice_id) diff --git a/backend/views/core/invoices/manage_access.py b/backend/views/core/invoices/manage_access.py index f6854309..22e20a05 100644 --- a/backend/views/core/invoices/manage_access.py +++ b/backend/views/core/invoices/manage_access.py @@ -1,12 +1,13 @@ from django.contrib import messages -from django.http import HttpRequest, HttpResponse +from django.http import HttpResponse from django.shortcuts import redirect, render from backend.decorators import quota_usage_check from backend.models import Invoice, InvoiceURL, QuotaUsage, QuotaLimit +from backend.types.htmx import HtmxHttpRequest -def manage_access(request: HttpRequest, invoice_id): +def manage_access(request: HtmxHttpRequest, invoice_id): try: invoice = Invoice.objects.prefetch_related("invoice_urls").get(id=invoice_id, user=request.user) except Invoice.DoesNotExist: @@ -23,7 +24,7 @@ def manage_access(request: HttpRequest, invoice_id): @quota_usage_check("invoices-access_codes", 1, api=True, htmx=True) -def create_code(request: HttpRequest, invoice_id): +def create_code(request: HtmxHttpRequest, invoice_id): if not request.htmx: return redirect("invoices:dashboard") @@ -48,7 +49,7 @@ def create_code(request: HttpRequest, invoice_id): ) -def delete_code(request: HttpRequest, code): +def delete_code(request: HtmxHttpRequest, code): if request.method != "DELETE" or not request.htmx: return HttpResponse("Request invalid", status=400) diff --git a/backend/views/core/invoices/overview.py b/backend/views/core/invoices/overview.py index 16e969fe..1b6dbade 100644 --- a/backend/views/core/invoices/overview.py +++ b/backend/views/core/invoices/overview.py @@ -1,16 +1,15 @@ -from django.http import HttpRequest - from backend.decorators import * from backend.models import * +from backend.types.htmx import HtmxHttpRequest -def invoices_dashboard(request: HttpRequest): +def invoices_dashboard(request: HtmxHttpRequest): context = {} return render(request, "pages/invoices/dashboard/dashboard.html", context) -def manage_invoice(request: HttpRequest, invoice_id: str): +def manage_invoice(request: HtmxHttpRequest, invoice_id: str): if not invoice_id.isnumeric(): messages.error(request, "Invalid invoice ID") return redirect("invoices:dashboard") diff --git a/backend/views/core/invoices/schedule.py b/backend/views/core/invoices/schedule.py index da82f2ce..89252545 100644 --- a/backend/views/core/invoices/schedule.py +++ b/backend/views/core/invoices/schedule.py @@ -1,13 +1,14 @@ from django.contrib import messages -from django.http import HttpRequest, HttpResponse +from django.http import HttpResponse from django.shortcuts import render, redirect from backend.decorators import feature_flag_check from backend.models import Invoice, QuotaLimit +from backend.types.htmx import HtmxHttpRequest @feature_flag_check("isInvoiceSchedulingEnabled", True) -def view_schedules(request: HttpRequest, invoice_id) -> HttpResponse: +def view_schedules(request: HtmxHttpRequest, invoice_id) -> HttpResponse: context = {} try: invoice = Invoice.objects.prefetch_related("onetime_invoice_schedules").get(id=invoice_id, user=request.user) diff --git a/backend/views/core/quotas/view.py b/backend/views/core/quotas/view.py index b2b8cc6a..40fae9f5 100644 --- a/backend/views/core/quotas/view.py +++ b/backend/views/core/quotas/view.py @@ -1,13 +1,14 @@ -from django.http import HttpResponse, HttpRequest +from django.http import HttpResponse from django.shortcuts import render from django.views.decorators.cache import cache_page from backend.decorators import superuser_only from backend.models import QuotaIncreaseRequest, QuotaLimit +from backend.types.htmx import HtmxHttpRequest @cache_page(3600) -def quotas_page(request: HttpRequest) -> HttpResponse: +def quotas_page(request: HtmxHttpRequest) -> HttpResponse: groups = list(QuotaLimit.objects.values_list("slug", flat=True).distinct()) quotas = set(q.split("-")[0] for q in groups if q.split("-")) @@ -20,11 +21,11 @@ def quotas_page(request: HttpRequest) -> HttpResponse: @cache_page(3600) -def quotas_list(request: HttpRequest, group: str) -> HttpResponse: +def quotas_list(request: HtmxHttpRequest, group: str) -> HttpResponse: return render(request, "pages/quotas/list.html", {"group": group}) @superuser_only -def view_quota_increase_requests(request: HttpRequest) -> HttpResponse: +def view_quota_increase_requests(request: HtmxHttpRequest) -> HttpResponse: requests = QuotaIncreaseRequest.objects.filter(status="pending").order_by("-created_at") return render(request, "pages/quotas/view_requests.html", {"requests": requests}) diff --git a/backend/views/core/receipts/dashboard.py b/backend/views/core/receipts/dashboard.py index d99da944..8fa0911c 100644 --- a/backend/views/core/receipts/dashboard.py +++ b/backend/views/core/receipts/dashboard.py @@ -1,8 +1,9 @@ from django.contrib.auth.decorators import login_required -from django.http import HttpRequest from django.shortcuts import render +from backend.types.htmx import HtmxHttpRequest + @login_required -def receipts_dashboard(request: HttpRequest): +def receipts_dashboard(request: HtmxHttpRequest): return render(request, "pages/receipts/dashboard.html") diff --git a/backend/views/core/settings/teams.py b/backend/views/core/settings/teams.py index b04f2b51..9527fd49 100644 --- a/backend/views/core/settings/teams.py +++ b/backend/views/core/settings/teams.py @@ -5,11 +5,11 @@ from django.shortcuts import render from backend.models import * +from backend.types.htmx import HtmxHttpRequest -def teams_dashboard(request: HttpRequest): - request.user: User = request.user # type: ignore[misc] - users_team: Optional[Team] = request.user.logged_in_as_team # type: ignore[union-attr, assignment] +def teams_dashboard(request: HtmxHttpRequest): + users_team: Optional[Team] = request.user.logged_in_as_team if not users_team: user_with_counts = User.objects.prefetch_related("teams_joined", "teams_leader_of").get(pk=request.user.pk) From 8ae6ad6ae94c28410e46a2d54cec4f22731dccb2 Mon Sep 17 00:00:00 2001 From: Domejko Date: Sat, 20 Apr 2024 15:54:53 +0200 Subject: [PATCH 11/12] HttpRequest replaced with authorized HtmxHttpRequest --- backend/api/receipts/fetch.py | 8 ++-- backend/api/settings/change_name.py | 12 +++--- backend/api/teams/leave.py | 9 ++-- backend/api/teams/switch_team.py | 41 +++++++++---------- backend/views/core/auth/passwords/generate.py | 10 ++--- 5 files changed, 37 insertions(+), 43 deletions(-) diff --git a/backend/api/receipts/fetch.py b/backend/api/receipts/fetch.py index 3954003c..0c8fb943 100644 --- a/backend/api/receipts/fetch.py +++ b/backend/api/receipts/fetch.py @@ -1,13 +1,13 @@ from django.db.models import Q -from django.http import HttpRequest from django.shortcuts import render, redirect from backend.models import Receipt, User +from backend.types.htmx import HtmxHttpRequest -def fetch_all_receipts(request: HttpRequest): +def fetch_all_receipts(request: HtmxHttpRequest): context = {} - if not request.htmx: # type: ignore[attr-defined] + if not request.htmx: return redirect("receipts dashboard") search_text = request.GET.get("search") @@ -23,7 +23,7 @@ def fetch_all_receipts(request: HttpRequest): } results = Receipt.objects.order_by("-date") - if isinstance(request.user, User) and request.user.logged_in_as_team: + if request.user.logged_in_as_team: results = results.filter(organization=request.user.logged_in_as_team) else: results = results.filter(user=request.user) diff --git a/backend/api/settings/change_name.py b/backend/api/settings/change_name.py index 9d4d3e71..d550a228 100644 --- a/backend/api/settings/change_name.py +++ b/backend/api/settings/change_name.py @@ -1,13 +1,15 @@ from django.contrib import messages from django.contrib.auth.models import AnonymousUser -from django.http import HttpRequest, HttpResponse +from django.http import HttpResponse from django.shortcuts import render from django.views.decorators.http import require_http_methods +from backend.types.htmx import HtmxHttpRequest + @require_http_methods(["POST"]) -def change_account_name(request: HttpRequest): - if not request.htmx: # type: ignore[attr-defined] +def change_account_name(request: HtmxHttpRequest): + if not request.htmx: return HttpResponse("Invalid Request", status=405) htmx_return = "base/toasts.html" @@ -19,10 +21,6 @@ def change_account_name(request: HttpRequest): messages.error(request, "Please enter a valid firstname or lastname.") return render(request, htmx_return) - if isinstance(request.user, AnonymousUser): - messages.error(request, "Please log in to change your account name.") - return render(request, htmx_return) - if request.user.first_name == first_name and request.user.last_name == last_name: messages.warning(request, "You already have this name.") return render(request, htmx_return) diff --git a/backend/api/teams/leave.py b/backend/api/teams/leave.py index 357e60be..7587605a 100644 --- a/backend/api/teams/leave.py +++ b/backend/api/teams/leave.py @@ -1,18 +1,19 @@ from django.contrib import messages -from django.http import HttpRequest, HttpResponse +from django.http import HttpResponse from django.shortcuts import render from backend.models import * +from backend.types.htmx import HtmxHttpRequest -def return_error_notif(request: HttpRequest, message: str): +def return_error_notif(request: HtmxHttpRequest, message: str): messages.error(request, message) resp = render(request, "partials/messages_list.html", status=200) resp["HX-Trigger-After-Swap"] = "leave_team_error" return resp -def leave_team_confirmed(request: HttpRequest, team_id): +def leave_team_confirmed(request: HtmxHttpRequest, team_id): team: Team | None = Team.objects.filter(id=team_id).first() if not team: @@ -21,7 +22,7 @@ def leave_team_confirmed(request: HttpRequest, team_id): if team.leader == request.user: # may be changed in the future. If no members allow delete return return_error_notif(request, "You cannot leave your own team") - if isinstance(request.user, User) and request.user.teams_joined.filter(id=team_id).exists(): + if request.user.teams_joined.filter(id=team_id).exists(): team.members.remove(request.user) messages.success(request, f"You have successfully left the team {team.name}") response = HttpResponse(status=200) diff --git a/backend/api/teams/switch_team.py b/backend/api/teams/switch_team.py index 90b3db68..74e6d79a 100644 --- a/backend/api/teams/switch_team.py +++ b/backend/api/teams/switch_team.py @@ -1,23 +1,23 @@ from django.contrib import messages from django.contrib.auth.models import AnonymousUser -from django.http import HttpRequest, HttpResponse +from django.http import HttpResponse from django.shortcuts import render from backend.models import Team +from backend.types.htmx import HtmxHttpRequest -def switch_team(request: HttpRequest, team_id): +def switch_team(request: HtmxHttpRequest, team_id): if not team_id: if not hasattr(request.user, "logged_in_as_team"): messages.error(request, "You are not logged into an organization") - if not isinstance(request.user, AnonymousUser): - request.user.logged_in_as_team = None - request.user.save() - messages.success(request, "You are now logged into your personal account") - response = HttpResponse(status=200) - response["HX-Refresh"] = "true" - return response + request.user.logged_in_as_team = None + request.user.save() + messages.success(request, "You are now logged into your personal account") + response = HttpResponse(status=200) + response["HX-Refresh"] = "true" + return response team: Team | None = Team.objects.filter(id=team_id).first() @@ -25,18 +25,17 @@ def switch_team(request: HttpRequest, team_id): messages.error(request, "Team not found") return render(request, "partials/messages_list.html") - if not isinstance(request.user, AnonymousUser): - if request.user.logged_in_as_team == team: - messages.error(request, "You are already logged in for this team") - return render(request, "partials/messages_list.html") + if request.user.logged_in_as_team == team: + messages.error(request, "You are already logged in for this team") + return render(request, "partials/messages_list.html") - if not request.user.teams_leader_of.filter(id=team_id).exists() and not request.user.teams_joined.filter(id=team_id).exists(): - messages.error(request, "You are not a member of this team") - return render(request, "partials/messages_list.html") + if not request.user.teams_leader_of.filter(id=team_id).exists() and not request.user.teams_joined.filter(id=team_id).exists(): + messages.error(request, "You are not a member of this team") + return render(request, "partials/messages_list.html") - messages.success(request, f"Now signing in for {team.name}") - request.user.logged_in_as_team = team - request.user.save() + messages.success(request, f"Now signing in for {team.name}") + request.user.logged_in_as_team = team + request.user.save() response = HttpResponse(status=200) response["HX-Refresh"] = "true" @@ -44,8 +43,8 @@ def switch_team(request: HttpRequest, team_id): # return render(request, "components/+logged_in_for.html") -def get_dropdown(request: HttpRequest): - if not request.htmx: # type: ignore[attr-defined] +def get_dropdown(request: HtmxHttpRequest): + if not request.htmx: return HttpResponse("Invalid Request", status=405) return render(request, "base/topbar/_organizations_list.html") diff --git a/backend/views/core/auth/passwords/generate.py b/backend/views/core/auth/passwords/generate.py index 259b3ddb..4356d0fe 100644 --- a/backend/views/core/auth/passwords/generate.py +++ b/backend/views/core/auth/passwords/generate.py @@ -4,12 +4,12 @@ from django.contrib.auth.hashers import make_password from django.core.exceptions import ValidationError from django.core.validators import validate_email -from django.http import HttpRequest from django.shortcuts import redirect from django.urls import reverse, resolve, NoReverseMatch from django.utils import timezone from backend.models import * +from backend.types.htmx import HtmxHttpRequest def msg_if_valid_email_then_sent(request): @@ -19,17 +19,13 @@ def msg_if_valid_email_then_sent(request): ) -def set_password_generate(request: HttpRequest): +def set_password_generate(request: HtmxHttpRequest): if not request.user.is_superuser or not request.user.is_staff: return redirect("dashboard") USER = request.GET.get("id") NEXT = request.GET.get("next") or "index" - if USER is None: - messages.error(request, "User ID is missing") - return redirect("dashboard") - if not USER.isnumeric(): messages.error(request, "User ID must be a valid integer") return redirect("dashboard") @@ -59,7 +55,7 @@ def set_password_generate(request: HttpRequest): return redirect("dashboard") -def password_reset(request: HttpRequest): +def password_reset(request: HtmxHttpRequest): EMAIL = request.POST.get("email") # if not EMAIL_SERVER_ENABLED: From acb7485896067ab4a2d12d67065ecc3e20133f9b Mon Sep 17 00:00:00 2001 From: Slawek Bierwiaczonek <119700507+Domejko@users.noreply.github.com> Date: Sat, 20 Apr 2024 16:11:24 +0200 Subject: [PATCH 12/12] Update switch_team.py --- backend/api/teams/switch_team.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/api/teams/switch_team.py b/backend/api/teams/switch_team.py index 74e6d79a..22cc7024 100644 --- a/backend/api/teams/switch_team.py +++ b/backend/api/teams/switch_team.py @@ -9,7 +9,7 @@ def switch_team(request: HtmxHttpRequest, team_id): if not team_id: - if not hasattr(request.user, "logged_in_as_team"): + if not request.user.logged_in_as_team: messages.error(request, "You are not logged into an organization") request.user.logged_in_as_team = None