From bdbd3c8c8c2701321d95290521272d1f6e1b7af7 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:05:42 +0100 Subject: [PATCH 01/16] Try adding guest user library --- adhocracy-plus/config/settings/base.py | 10 ++++++++++ adhocracy-plus/config/urls.py | 1 + apps/projects/views.py | 4 +++- apps/users/__init__.py | 6 ++++++ apps/users/apps.py | 9 +++++++++ apps/users/guests.py | 12 ++++++++++++ apps/users/models.py | 24 ++++++++++++++++++++++++ 7 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 apps/users/guests.py diff --git a/adhocracy-plus/config/settings/base.py b/adhocracy-plus/config/settings/base.py index 94395b2b0..f8af8155a 100644 --- a/adhocracy-plus/config/settings/base.py +++ b/adhocracy-plus/config/settings/base.py @@ -34,6 +34,8 @@ "widget_tweaks", "rest_framework", "rest_framework.authtoken", + "guest_user", + "guest_user.contrib.allauth", # JWT authentication "rest_framework_simplejwt.token_blacklist", "django_filters", @@ -276,6 +278,7 @@ "rules.permissions.ObjectPermissionBackend", "django.contrib.auth.backends.ModelBackend", "allauth.account.auth_backends.AuthenticationBackend", + "guest_user.backends.GuestBackend", ) ACCOUNT_ADAPTER = "apps.users.adapters.AccountAdapter" @@ -705,3 +708,10 @@ }, }, } + +GUEST_USER = { + 'NAME_GENERATOR': 'guest_user.functions.generate_uuid_username', # Default option +} + +GUEST_USER_MODEL = "a4_candy_users.CustomGuestUser" +# GUEST_USER_MODEL = "users.CustomGuestUser" \ No newline at end of file diff --git a/adhocracy-plus/config/urls.py b/adhocracy-plus/config/urls.py index 649224815..ae6f4d0a3 100644 --- a/adhocracy-plus/config/urls.py +++ b/adhocracy-plus/config/urls.py @@ -97,6 +97,7 @@ path("documents/", include(wagtaildocs_urls)), path("accounts/", include("allauth.urls")), path("account/", include("apps.account.urls")), + path("convert/", include("guest_user.urls")), path("profile/", include("apps.users.urls")), path("userdashboard/", include("apps.userdashboard.urls")), # this needs to be above i18n/ to make the overwrite work diff --git a/apps/projects/views.py b/apps/projects/views.py index ac3a0ff84..c538d2e68 100644 --- a/apps/projects/views.py +++ b/apps/projects/views.py @@ -6,12 +6,14 @@ from django.shortcuts import get_object_or_404 from django.shortcuts import redirect from django.urls import reverse_lazy +from django.utils.decorators import method_decorator from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext from django.views import generic from rules.contrib.views import LoginRequiredMixin from rules.contrib.views import PermissionRequiredMixin +from guest_user.decorators import allow_guest_user from adhocracy4.dashboard import mixins as a4dashboard_mixins from adhocracy4.dashboard import signals as a4dashboard_signals @@ -271,7 +273,7 @@ def form_valid(self, request, *args, **kwargs): messages.success(self.request, self.success_message % obj.__dict__) return super().form_valid(request, *args, **kwargs) - +@method_decorator(allow_guest_user, name='dispatch') class ProjectDetailView( PermissionRequiredMixin, ProjectModuleDispatchMixin, diff --git a/apps/users/__init__.py b/apps/users/__init__.py index 57214b176..26a048165 100644 --- a/apps/users/__init__.py +++ b/apps/users/__init__.py @@ -1,5 +1,11 @@ from django.contrib.auth.signals import user_logged_in from django.utils.translation import gettext_lazy as _ +# from .guests import CustomGuestUser +# import guests +# __all__ = ['CustomGuest'] + +# default_app_config = "users.apps.UsersConfig" + USERNAME_REGEX = r"^[\w]+[ \w.@+-]*$" USERNAME_INVALID_MESSAGE = _( diff --git a/apps/users/apps.py b/apps/users/apps.py index 5f9e6f13c..3c242925f 100644 --- a/apps/users/apps.py +++ b/apps/users/apps.py @@ -4,3 +4,12 @@ class Config(AppConfig): name = "apps.users" label = "a4_candy_users" + + +# class UsersConfig(AppConfig): +# name = "apps.users" +# label = "a4_candy_users" + +# def ready(self): +# # Import the model here to ensure it's registered +# from .guests import CustomGuestUser diff --git a/apps/users/guests.py b/apps/users/guests.py new file mode 100644 index 000000000..30fdce4d3 --- /dev/null +++ b/apps/users/guests.py @@ -0,0 +1,12 @@ +# # from apps.users.models import User +# from guest_user.models import Guest, GuestManager + + +# class CustomGuestManager(GuestManager): +# def create_guest_user(self, request=None, username=None): +# # is just User in the example... +# user = Guest.objects.create(username=username, email='guest' + username + '@liqd.net') +# return user + +# class CustomGuestUser(Guest): +# objects = CustomGuestManager() \ No newline at end of file diff --git a/apps/users/models.py b/apps/users/models.py index 1cb0959b9..8c6b55247 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -12,10 +12,34 @@ from adhocracy4.projects.enums import Access from adhocracy4.projects.models import Project from apps.organisations.models import OrganisationTermsOfUse +# from .guests import CustomGuestUser +# __all__ = ['CustomGuest'] from . import USERNAME_INVALID_MESSAGE from . import USERNAME_REGEX +from guest_user.models import Guest, GuestManager + +# didn't work +# from django.utils.module_loading import import_string + +# def get_guest_model(): +# Guest = import_string('guest_user.models.Guest') +# return Guest + +# def get_guest_manager(): +# GuestManager = import_string('guest_user.models.GuestManager') +# return GuestManager + +class CustomGuestManager(GuestManager): + def create_guest_user(self, request=None, username=None): + # is just User in the example... + user = CustomGuestUser.objects.create(username=username, email='guest' + username + '@liqd.net') + return user + +class CustomGuestUser(Guest): + objects = CustomGuestManager() + class User(auth_models.AbstractBaseUser, auth_models.PermissionsMixin): username = models.CharField( From dc4a6616c2d8691471c71d374748c16bd7aefd26 Mon Sep 17 00:00:00 2001 From: Mara Karagianni Date: Thu, 13 Feb 2025 14:59:14 +0100 Subject: [PATCH 02/16] work with forked guest-uesr lib --- adhocracy-plus/config/settings/base.py | 8 ++------ apps/users/__init__.py | 6 ------ apps/users/apps.py | 9 --------- apps/users/guests.py | 12 ------------ apps/users/models.py | 25 +------------------------ requirements/base.txt | 1 + 6 files changed, 4 insertions(+), 57 deletions(-) delete mode 100644 apps/users/guests.py diff --git a/adhocracy-plus/config/settings/base.py b/adhocracy-plus/config/settings/base.py index f8af8155a..e462a8362 100644 --- a/adhocracy-plus/config/settings/base.py +++ b/adhocracy-plus/config/settings/base.py @@ -34,14 +34,13 @@ "widget_tweaks", "rest_framework", "rest_framework.authtoken", - "guest_user", - "guest_user.contrib.allauth", # JWT authentication "rest_framework_simplejwt.token_blacklist", "django_filters", "allauth", "allauth.account", "allauth.socialaccount", + "guest_user", "rules.apps.AutodiscoverRulesConfig", "easy_thumbnails", "parler", @@ -710,8 +709,5 @@ } GUEST_USER = { - 'NAME_GENERATOR': 'guest_user.functions.generate_uuid_username', # Default option + "NAME_GENERATOR": "guest_user.functions.generate_uuid_username", # Default option } - -GUEST_USER_MODEL = "a4_candy_users.CustomGuestUser" -# GUEST_USER_MODEL = "users.CustomGuestUser" \ No newline at end of file diff --git a/apps/users/__init__.py b/apps/users/__init__.py index 26a048165..57214b176 100644 --- a/apps/users/__init__.py +++ b/apps/users/__init__.py @@ -1,11 +1,5 @@ from django.contrib.auth.signals import user_logged_in from django.utils.translation import gettext_lazy as _ -# from .guests import CustomGuestUser -# import guests -# __all__ = ['CustomGuest'] - -# default_app_config = "users.apps.UsersConfig" - USERNAME_REGEX = r"^[\w]+[ \w.@+-]*$" USERNAME_INVALID_MESSAGE = _( diff --git a/apps/users/apps.py b/apps/users/apps.py index 3c242925f..5f9e6f13c 100644 --- a/apps/users/apps.py +++ b/apps/users/apps.py @@ -4,12 +4,3 @@ class Config(AppConfig): name = "apps.users" label = "a4_candy_users" - - -# class UsersConfig(AppConfig): -# name = "apps.users" -# label = "a4_candy_users" - -# def ready(self): -# # Import the model here to ensure it's registered -# from .guests import CustomGuestUser diff --git a/apps/users/guests.py b/apps/users/guests.py deleted file mode 100644 index 30fdce4d3..000000000 --- a/apps/users/guests.py +++ /dev/null @@ -1,12 +0,0 @@ -# # from apps.users.models import User -# from guest_user.models import Guest, GuestManager - - -# class CustomGuestManager(GuestManager): -# def create_guest_user(self, request=None, username=None): -# # is just User in the example... -# user = Guest.objects.create(username=username, email='guest' + username + '@liqd.net') -# return user - -# class CustomGuestUser(Guest): -# objects = CustomGuestManager() \ No newline at end of file diff --git a/apps/users/models.py b/apps/users/models.py index 8c6b55247..4c1802d6a 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -12,34 +12,10 @@ from adhocracy4.projects.enums import Access from adhocracy4.projects.models import Project from apps.organisations.models import OrganisationTermsOfUse -# from .guests import CustomGuestUser -# __all__ = ['CustomGuest'] from . import USERNAME_INVALID_MESSAGE from . import USERNAME_REGEX -from guest_user.models import Guest, GuestManager - -# didn't work -# from django.utils.module_loading import import_string - -# def get_guest_model(): -# Guest = import_string('guest_user.models.Guest') -# return Guest - -# def get_guest_manager(): -# GuestManager = import_string('guest_user.models.GuestManager') -# return GuestManager - -class CustomGuestManager(GuestManager): - def create_guest_user(self, request=None, username=None): - # is just User in the example... - user = CustomGuestUser.objects.create(username=username, email='guest' + username + '@liqd.net') - return user - -class CustomGuestUser(Guest): - objects = CustomGuestManager() - class User(auth_models.AbstractBaseUser, auth_models.PermissionsMixin): username = models.CharField( @@ -150,6 +126,7 @@ class User(auth_models.AbstractBaseUser, auth_models.PermissionsMixin): objects = auth_models.UserManager() USERNAME_FIELD = "username" + PASSWORD_FIELD = "username" REQUIRED_FIELDS = ["email"] def get_projects_follow_list(self, exclude_private_projects=False): diff --git a/requirements/base.txt b/requirements/base.txt index 20b1f316f..aec8e8aa7 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,6 +5,7 @@ git+https://github.com/liqd/adhocracy4.git@aplus-v2501.1.2#egg=adhocracy4 brotli==1.1.0 django-cloudflare-push==0.2.2 django_csp==3.8 +django-guest-user @ git+https://github.com/liqd/django-guest-user.git@mk-2025-add-email-password-to-guest-user django-parler==2.3 djangorestframework-simplejwt==5.3.1 sentry-sdk==2.19.2 From fe47ac85107a770e930b1276fca873555f23b85b Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:53:03 +0100 Subject: [PATCH 03/16] Add start guest creation form --- adhocracy-plus/config/settings/base.py | 3 ++- adhocracy-plus/config/urls.py | 4 +++- apps/users/forms.py | 22 ++++++++++++++++++++++ apps/users/views.py | 7 +++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/adhocracy-plus/config/settings/base.py b/adhocracy-plus/config/settings/base.py index e462a8362..5a85d6d68 100644 --- a/adhocracy-plus/config/settings/base.py +++ b/adhocracy-plus/config/settings/base.py @@ -302,7 +302,8 @@ SOCIALACCOUNT_EMAIL_AUTHENTICATION = True SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True -LOGIN_URL = "account_login" +# LOGIN_URL = "account_login" +LOGIN_URL = "guest_create" LOGIN_REDIRECT_URL = "/" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" diff --git a/adhocracy-plus/config/urls.py b/adhocracy-plus/config/urls.py index ae6f4d0a3..8ac31d047 100644 --- a/adhocracy-plus/config/urls.py +++ b/adhocracy-plus/config/urls.py @@ -43,6 +43,7 @@ from apps.userdashboard.routers import ModerationDetailDefaultRouter from apps.users.api import UserViewSet from apps.users.decorators import user_is_project_admin +from apps.users.views import GuestCreateView from apps.users.views import set_language_overwrite router = routers.DefaultRouter() @@ -95,9 +96,10 @@ re_path(r"^django-admin/", admin.site.urls), path("admin/", include("wagtail.admin.urls")), path("documents/", include(wagtaildocs_urls)), + path("convert/", include("guest_user.urls")), + path("accounts/guests/login", GuestCreateView.as_view(), name="guest_create"), path("accounts/", include("allauth.urls")), path("account/", include("apps.account.urls")), - path("convert/", include("guest_user.urls")), path("profile/", include("apps.users.urls")), path("userdashboard/", include("apps.userdashboard.urls")), # this needs to be above i18n/ to make the overwrite work diff --git a/apps/users/forms.py b/apps/users/forms.py index 5fd349477..467b6ecaf 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -9,6 +9,7 @@ from django.contrib.auth import forms as auth_forms from django.utils.translation import get_language from django.utils.translation import gettext_lazy as _ +from guest_user.functions import maybe_create_guest_user from zeep import Client from apps.captcha.fields import CaptcheckCaptchaField @@ -77,6 +78,27 @@ def save(self, request): return user +class GuestCreateForm(SignupForm): + terms_of_use = forms.BooleanField(label=_("Terms of use")) + captcha = CaptcheckCaptchaField(label=_("I am not a robot")) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + del self.fields["username"] + del self.fields["email"] + del self.fields["password1"] + del self.fields["password2"] + if not (hasattr(settings, "CAPTCHA_URL") and settings.CAPTCHA_URL): + del self.fields["captcha"] + else: + self.fields["captcha"].help_text = helpers.add_email_link_to_helptext( + self.fields["captcha"].help_text, CAPTCHA_HELP + ) + + def save(self, request): + maybe_create_guest_user(request) + + class IgbceSignupForm(DefaultSignupForm): member_number = forms.IntegerField( label=_("Membership number of IG BCE"), diff --git a/apps/users/views.py b/apps/users/views.py index 8e6c57a89..bff4a0637 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -1,3 +1,4 @@ +from allauth.account.views import SignupView from django.utils.translation import check_for_language from django.views.generic.detail import DetailView from django.views.i18n import LANGUAGE_QUERY_PARAMETER @@ -7,6 +8,12 @@ from apps.organisations.models import Organisation from . import models +from .forms import GuestCreateForm + + +class GuestCreateView(SignupView): + form_class = GuestCreateForm + template_name = "account/signup.html" class ProfileView(DetailView): From 450ff1adce5993cccc40aa8dadd40781363e3438 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:39:53 +0100 Subject: [PATCH 04/16] Add create and logs in as guest --- apps/users/forms.py | 43 +++++++++--- .../a4_candy_users/guest_create.html | 70 +++++++++++++++++++ apps/users/views.py | 18 ++++- 3 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 apps/users/templates/a4_candy_users/guest_create.html diff --git a/apps/users/forms.py b/apps/users/forms.py index 467b6ecaf..4dde22acb 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -9,7 +9,8 @@ from django.contrib.auth import forms as auth_forms from django.utils.translation import get_language from django.utils.translation import gettext_lazy as _ -from guest_user.functions import maybe_create_guest_user + +# from guest_user.functions import maybe_create_guest_user from zeep import Client from apps.captcha.fields import CaptcheckCaptchaField @@ -51,6 +52,7 @@ class DefaultSignupForm(SignupForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + print("Initializing default signup") # Debug statement self.fields["username"].help_text = _( "Your username will appear publicly next to your posts." ) @@ -78,16 +80,42 @@ def save(self, request): return user -class GuestCreateForm(SignupForm): +# class GuestCreateForm(SignupForm): +# terms_of_use = forms.BooleanField(label=_("Terms of use")) +# captcha = CaptcheckCaptchaField(label=_("I am not a robot")) + +# def __init__(self, *args, **kwargs): +# super().__init__(*args, **kwargs) +# print("Initializing GuestCreateForm") # Debug statement +# del self.fields["username"] +# del self.fields["email"] +# del self.fields["password1"] +# del self.fields["password2"] +# if not (hasattr(settings, "CAPTCHA_URL") and settings.CAPTCHA_URL): +# del self.fields["captcha"] +# else: +# self.fields["captcha"].help_text = helpers.add_email_link_to_helptext( +# self.fields["captcha"].help_text, CAPTCHA_HELP +# ) + +# def save(self, request): +# maybe_create_guest_user(request) + +# from django import forms +# from django.utils.translation import gettext_lazy as _ +# from captcheck.fields import CaptcheckCaptchaField +# from django.conf import settings + + +class GuestCreateForm(forms.Form): terms_of_use = forms.BooleanField(label=_("Terms of use")) captcha = CaptcheckCaptchaField(label=_("I am not a robot")) + # next = forms.CharField(widget=forms.HiddenInput, required=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - del self.fields["username"] - del self.fields["email"] - del self.fields["password1"] - del self.fields["password2"] + + # Remove captcha if not configured if not (hasattr(settings, "CAPTCHA_URL") and settings.CAPTCHA_URL): del self.fields["captcha"] else: @@ -95,9 +123,6 @@ def __init__(self, *args, **kwargs): self.fields["captcha"].help_text, CAPTCHA_HELP ) - def save(self, request): - maybe_create_guest_user(request) - class IgbceSignupForm(DefaultSignupForm): member_number = forms.IntegerField( diff --git a/apps/users/templates/a4_candy_users/guest_create.html b/apps/users/templates/a4_candy_users/guest_create.html new file mode 100644 index 000000000..0dac3643a --- /dev/null +++ b/apps/users/templates/a4_candy_users/guest_create.html @@ -0,0 +1,70 @@ +{% extends "account/base.html" %} +{% load i18n %} +{% block head_title %} + {% translate "Continue as Guest" %} +{% endblock head_title %} +{% block content %} +

Continue as Guest

+

+ {% blocktranslate %}Already have an account? Then please + login.{% endblocktranslate %} + {% blocktranslate %}You want to create a user account? Then please + create account.{% endblocktranslate %} +

+
+ {{ form.non_field_errors }} + {{ form.media }} + {% csrf_token %} + {% for field in form %} + {% if field is not form.terms_of_use and field is not form.terms_of_use_extra and field is not form.get_newsletters and field is not form.captcha %} + {% include 'a4_candy_contrib/includes/form_field.html' with field=field %} + {% endif %} + {% endfor %} +
+ + {{ form.terms_of_use.errors }} +
+ {% if form.terms_of_use_extra %} +
+ + {{ form.terms_of_use.errors }} +
+ {% endif %} +
+ +
{{ form.get_newsletters.help_text }}
+ {{ form.get_newsletters.errors }} +
+ {% if redirect_field_value %} + + {% endif %} + {% if form.captcha %} + {% with tabindex="0" %} + {% include 'a4_candy_contrib/includes/form_field.html' with field=form.captcha tabindex=0 %} + {% endwith %} + {% endif %} + +
+ +
+
+{% endblock content %} diff --git a/apps/users/views.py b/apps/users/views.py index bff4a0637..6722097f1 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -1,8 +1,9 @@ -from allauth.account.views import SignupView from django.utils.translation import check_for_language +from django.views.generic import FormView from django.views.generic.detail import DetailView from django.views.i18n import LANGUAGE_QUERY_PARAMETER from django.views.i18n import set_language +from guest_user.functions import maybe_create_guest_user from adhocracy4.actions.models import Action from apps.organisations.models import Organisation @@ -10,10 +11,21 @@ from . import models from .forms import GuestCreateForm +# class GuestCreateView(SignupView): +# form_class = GuestCreateForm +# template_name = "a4_candy_users/guest_create.html" -class GuestCreateView(SignupView): + +class GuestCreateView(FormView): form_class = GuestCreateForm - template_name = "account/signup.html" + template_name = "a4_candy_users/guest_create.html" + success_url = "/" + + def form_valid(self, form): + print("got to save") + print(self.request) # Access the request object via self.request + maybe_create_guest_user(self.request) # Pass self.request to the function + return super().form_valid(form) # Don't forget to call the parent method class ProfileView(DetailView): From df156c8a2a22e74f7289dc0db9e1e557c1fc47f4 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Wed, 19 Feb 2025 17:05:16 +0100 Subject: [PATCH 05/16] Add guest login with next url --- adhocracy-plus/templates/account/login.html | 16 ++++++---- apps/projects/views.py | 4 +-- .../a4_candy_users/guest_create.html | 1 + apps/users/views.py | 29 +++++++++++++++---- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/adhocracy-plus/templates/account/login.html b/adhocracy-plus/templates/account/login.html index 1c62e51f5..f47768d6f 100644 --- a/adhocracy-plus/templates/account/login.html +++ b/adhocracy-plus/templates/account/login.html @@ -1,14 +1,20 @@ {% extends "account/base.html" %} -{% load i18n account %} +{% load i18n account %} -{% block head_title %}{% translate "Login" %}{% endblock %} +{% block head_title %}{% translate "Login" %}{% endblock head_title %} {% block content %}

{% translate "Login" %}

{% blocktranslate %}If you have not created an account yet, then please register first.{% endblocktranslate %}

- -
+ {% url 'guest_create' as guest_create_url %} + {% with next_param=request.GET.next|urlencode %} + {% with guest_url=guest_create_url|add:"?next="|add:next_param %} +

{% blocktranslate %}Sie wollen als Gast fortfahren? Dann klicken Sie hier.{% endblocktranslate %}

+ {% endwith %} + {% endwith %} + + {{ form.non_field_errors }} {% csrf_token %} @@ -33,4 +39,4 @@

{% translate "Login" %}

{% include "socialaccount/snippets/provider_list.html" with process="login" %} -{% endblock %} +{% endblock content %} diff --git a/apps/projects/views.py b/apps/projects/views.py index c538d2e68..ac3a0ff84 100644 --- a/apps/projects/views.py +++ b/apps/projects/views.py @@ -6,14 +6,12 @@ from django.shortcuts import get_object_or_404 from django.shortcuts import redirect from django.urls import reverse_lazy -from django.utils.decorators import method_decorator from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext from django.views import generic from rules.contrib.views import LoginRequiredMixin from rules.contrib.views import PermissionRequiredMixin -from guest_user.decorators import allow_guest_user from adhocracy4.dashboard import mixins as a4dashboard_mixins from adhocracy4.dashboard import signals as a4dashboard_signals @@ -273,7 +271,7 @@ def form_valid(self, request, *args, **kwargs): messages.success(self.request, self.success_message % obj.__dict__) return super().form_valid(request, *args, **kwargs) -@method_decorator(allow_guest_user, name='dispatch') + class ProjectDetailView( PermissionRequiredMixin, ProjectModuleDispatchMixin, diff --git a/apps/users/templates/a4_candy_users/guest_create.html b/apps/users/templates/a4_candy_users/guest_create.html index 0dac3643a..d9b58668b 100644 --- a/apps/users/templates/a4_candy_users/guest_create.html +++ b/apps/users/templates/a4_candy_users/guest_create.html @@ -62,6 +62,7 @@

Continue as Guest

{% include 'a4_candy_contrib/includes/form_field.html' with field=form.captcha tabindex=0 %} {% endwith %} {% endif %} +
diff --git a/apps/users/views.py b/apps/users/views.py index 6722097f1..772e16a01 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -11,15 +11,34 @@ from . import models from .forms import GuestCreateForm -# class GuestCreateView(SignupView): -# form_class = GuestCreateForm -# template_name = "a4_candy_users/guest_create.html" - class GuestCreateView(FormView): form_class = GuestCreateForm template_name = "a4_candy_users/guest_create.html" - success_url = "/" + + def get_success_url(self): + next_url = self.request.POST.get("next") + + if not next_url: + next_url = self.request.GET.get("next") + if not next_url: + next_url = "/" + + print("next url " + next_url) + return next_url + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + print(context) + print(self.request.GET.get("next", "")) + context["next"] = self.request.GET.get("next", "") + return context + + def get_initial(self): + initial = super().get_initial() + + initial["next"] = self.request.GET.get("next", "") + return initial def form_valid(self, form): print("got to save") From 4ca3152aba1cf3f6cf54fd5def8101bfd05b018f Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Wed, 19 Feb 2025 17:36:07 +0100 Subject: [PATCH 06/16] Remove urlencode to fix append bug --- adhocracy-plus/templates/account/login.html | 2 +- adhocracy-plus/templates/account/signup.html | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/adhocracy-plus/templates/account/login.html b/adhocracy-plus/templates/account/login.html index f47768d6f..58f6d5d94 100644 --- a/adhocracy-plus/templates/account/login.html +++ b/adhocracy-plus/templates/account/login.html @@ -8,7 +8,7 @@

{% translate "Login" %}

{% blocktranslate %}If you have not created an account yet, then please register first.{% endblocktranslate %}

{% url 'guest_create' as guest_create_url %} - {% with next_param=request.GET.next|urlencode %} + {% with next_param=request.GET.next %} {% with guest_url=guest_create_url|add:"?next="|add:next_param %}

{% blocktranslate %}Sie wollen als Gast fortfahren? Dann klicken Sie hier.{% endblocktranslate %}

{% endwith %} diff --git a/adhocracy-plus/templates/account/signup.html b/adhocracy-plus/templates/account/signup.html index 93d963632..3fc573047 100644 --- a/adhocracy-plus/templates/account/signup.html +++ b/adhocracy-plus/templates/account/signup.html @@ -2,7 +2,7 @@ {% load i18n %} {% block head_title %} {% translate "Register" %} -{% endblock %} +{% endblock head_title %} {% block content %}

{% translate "Register" %}

{% with page=settings.a4_candy_cms_settings.ImportantPages.registration %} @@ -14,6 +14,15 @@

{% translate "Register" %}

{% blocktranslate %}Already have an account? Then please login.{% endblocktranslate %}

+ {% url 'guest_create' as guest_create_url %} + {% with next_param=request.GET.next %} + {% with guest_url=guest_create_url|add:"?next="|add:next_param %} +

+ {% blocktranslate %}Sie wollen als Gast fortfahren? Dann klicken Sie + hier.{% endblocktranslate %} +

+ {% endwith %} + {% endwith %}
{{ form.non_field_errors }} {{ form.media }} @@ -71,4 +80,4 @@

{% translate "Register" %}

{% include "socialaccount/snippets/provider_list.html" with process="login" %} -{% endblock %} +{% endblock content %} From 2adc77f93033a8bd98db2982b6432a878fbde4d4 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Mon, 3 Mar 2025 18:40:22 +0100 Subject: [PATCH 07/16] Add login and signup links to guest form --- apps/users/templates/a4_candy_users/guest_create.html | 10 +++++++++- apps/users/views.py | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/users/templates/a4_candy_users/guest_create.html b/apps/users/templates/a4_candy_users/guest_create.html index d9b58668b..49a797236 100644 --- a/apps/users/templates/a4_candy_users/guest_create.html +++ b/apps/users/templates/a4_candy_users/guest_create.html @@ -6,10 +6,18 @@ {% block content %}

Continue as Guest

+ {% url 'account_login' as login_url %} + {% url 'account_signup' as signup_url %} + {% with next_param=request.GET.next %} + {% with login_url=login_url|add:"?next="|add:next_param %} + {% with signup_url=signup_url|add:"?next="|add:next_param %} {% blocktranslate %}Already have an account? Then please login.{% endblocktranslate %} {% blocktranslate %}You want to create a user account? Then please - create account.{% endblocktranslate %} + create account.{% endblocktranslate %} + {% endwith %} + {% endwith %} + {% endwith %}

{{ form.non_field_errors }} diff --git a/apps/users/views.py b/apps/users/views.py index 772e16a01..318f67aae 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -32,6 +32,7 @@ def get_context_data(self, **kwargs): print(context) print(self.request.GET.get("next", "")) context["next"] = self.request.GET.get("next", "") + # context["login_url"] = account_urls.login_url return context def get_initial(self): From 2ed731724a8cf4521bc00c9cab08bd60ae940999 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Mon, 3 Mar 2025 18:40:45 +0100 Subject: [PATCH 08/16] Add guest user helper func and private topic test --- tests/helpers.py | 23 ++++++++++++ tests/topicprio/views/test_topic_detail.py | 42 ++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/tests/helpers.py b/tests/helpers.py index 7a240264f..5d245188e 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,4 +1,27 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.models import AnonymousUser +from django.contrib.sessions.backends.db import SessionStore from django.core import mail +from django.test import RequestFactory +from guest_user.functions import maybe_create_guest_user + +User = get_user_model() + + +class GuestUserCreator: + def __init__(self): + self.request_factory = RequestFactory() + + def create_guest_user(self): + request = self.request_factory.get("/") + request.user = AnonymousUser() + request.session = SessionStore() + request.session.create() + + maybe_create_guest_user(request) + + print(User.objects.latest("date_joined").username) + return User.objects.latest("date_joined") def get_emails_for_address(email_address): diff --git a/tests/topicprio/views/test_topic_detail.py b/tests/topicprio/views/test_topic_detail.py index 23b1f7ded..bce424cd9 100644 --- a/tests/topicprio/views/test_topic_detail.py +++ b/tests/topicprio/views/test_topic_detail.py @@ -8,6 +8,7 @@ from adhocracy4.test.helpers import redirect_target from adhocracy4.test.helpers import setup_phase from apps.topicprio import phases +from tests.helpers import GuestUserCreator @pytest.mark.django_db @@ -53,6 +54,47 @@ def test_detail_view_private_not_visible_normal_user( assert response.status_code == 403 +# @pytest.mark.django_db +# def test_with_guest_user(guest_user): +# assert guest_user.is_guest is True +# assert guest_user.username.startswith("guest") +# assert False + + +@pytest.mark.django_db +def test_detail_view_private_not_visible_guest_user( + client, user, phase_factory, topic_factory +): + phase, module, project, topic = setup_phase( + phase_factory, topic_factory, phases.PrioritizePhase + ) + topic.module.project.access = Access.PRIVATE + topic.module.project.save() + + guest_user_creator = GuestUserCreator() + guest_user = guest_user_creator.create_guest_user() + + assert user not in topic.module.project.participants.all() + client.force_login(guest_user) + + url = topic.get_absolute_url() + response = client.get(url) + assert response.status_code == 403 + + +# @pytest.mark.django_db +# def test_detail_view_private_not_visible_guest_user( +# client, user, phase_factory, topic_factory, guest_user +# ): +# # Log in as the guest user +# client.force_login(guest_user) + +# # Your test logic here +# response = client.get("/some-url/") +# assert response.status_code == 200 +# assert "Private content" not in response.content.decode() + + @pytest.mark.django_db def test_detail_view_private_visible_to_participant( client, user, phase_factory, topic_factory From 14be6106856b163a47c456256f1c747dd584374c Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:46:13 +0100 Subject: [PATCH 09/16] Add test assert guest user created --- tests/topicprio/views/test_topic_detail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/topicprio/views/test_topic_detail.py b/tests/topicprio/views/test_topic_detail.py index bce424cd9..4e187aefe 100644 --- a/tests/topicprio/views/test_topic_detail.py +++ b/tests/topicprio/views/test_topic_detail.py @@ -1,6 +1,7 @@ import pytest from django.contrib.contenttypes.models import ContentType from django.urls import reverse +from guest_user.functions import is_guest_user from adhocracy4.projects.enums import Access from adhocracy4.test.helpers import assert_template_response @@ -73,10 +74,10 @@ def test_detail_view_private_not_visible_guest_user( guest_user_creator = GuestUserCreator() guest_user = guest_user_creator.create_guest_user() + assert is_guest_user(guest_user) assert user not in topic.module.project.participants.all() client.force_login(guest_user) - url = topic.get_absolute_url() response = client.get(url) assert response.status_code == 403 From 2684e65c1318a492f2f293d8ef9ef8ffc572384c Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:47:38 +0100 Subject: [PATCH 10/16] Clean up code --- tests/helpers.py | 2 -- tests/topicprio/views/test_topic_detail.py | 20 -------------------- 2 files changed, 22 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index 5d245188e..c0f6057c5 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -19,8 +19,6 @@ def create_guest_user(self): request.session.create() maybe_create_guest_user(request) - - print(User.objects.latest("date_joined").username) return User.objects.latest("date_joined") diff --git a/tests/topicprio/views/test_topic_detail.py b/tests/topicprio/views/test_topic_detail.py index 4e187aefe..ebfa94c3b 100644 --- a/tests/topicprio/views/test_topic_detail.py +++ b/tests/topicprio/views/test_topic_detail.py @@ -55,13 +55,6 @@ def test_detail_view_private_not_visible_normal_user( assert response.status_code == 403 -# @pytest.mark.django_db -# def test_with_guest_user(guest_user): -# assert guest_user.is_guest is True -# assert guest_user.username.startswith("guest") -# assert False - - @pytest.mark.django_db def test_detail_view_private_not_visible_guest_user( client, user, phase_factory, topic_factory @@ -83,19 +76,6 @@ def test_detail_view_private_not_visible_guest_user( assert response.status_code == 403 -# @pytest.mark.django_db -# def test_detail_view_private_not_visible_guest_user( -# client, user, phase_factory, topic_factory, guest_user -# ): -# # Log in as the guest user -# client.force_login(guest_user) - -# # Your test logic here -# response = client.get("/some-url/") -# assert response.status_code == 200 -# assert "Private content" not in response.content.decode() - - @pytest.mark.django_db def test_detail_view_private_visible_to_participant( client, user, phase_factory, topic_factory From e3af2b0aee26a0dc7eb0cf0a6e7496a97bd784b3 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:59:08 +0100 Subject: [PATCH 11/16] Add draft access guest with admin --- tests/users/test_views.py | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/users/test_views.py b/tests/users/test_views.py index c6b99b5a6..f53a12374 100644 --- a/tests/users/test_views.py +++ b/tests/users/test_views.py @@ -3,15 +3,29 @@ import pytest from allauth.account.models import EmailAddress from django.contrib import auth +from django.contrib.auth.models import AnonymousUser from django.core import mail from django.test import override_settings from django.urls import reverse from django.utils import translation +# from django.test import Client +from guest_user.functions import get_guest_model +from guest_user.functions import is_guest_user + from adhocracy4.test.helpers import redirect_target from apps.users import models +from tests.helpers import GuestUserCreator + +# from django.contrib.sessions.backends.db import SessionStore +# from django.test import RequestFactory + +# from django.contrib.sessions.middleware import SessionMiddleware +# from django.contrib.auth.middleware import AuthenticationMiddleware +# from django.test import RequestFactory User = auth.get_user_model() +GuestUser = get_guest_model() @override_settings(LANGUAGE_CODE="de") @@ -258,3 +272,44 @@ def test_profile(client, user): response = client.get(url) assert response.status_code == 200 assert response.context["user"] == user + + +@pytest.mark.django_db +def test_admin_can_access_guest_user(admin, apiclient, logout_url): + # Create a guest user + guest_user_creator = GuestUserCreator() + guest_user = guest_user_creator.create_guest_user() + assert is_guest_user(guest_user) + + # Log out of the guest session (clear session and authentication) + apiclient.logout() # Clear authentication + if hasattr(apiclient, "session"): + apiclient.session.flush() # Clear the session + + # Assert that we're logged out + # 1. Check authentication state by making a request and inspecting the user + response = apiclient.get("/some-public-endpoint/") # Use a public endpoint + assert isinstance( + response.wsgi_request.user, AnonymousUser + ) # Ensure user is AnonymousUser + + # 2. Check session state + if hasattr(apiclient, "session"): + assert not apiclient.session.session_key # Ensure no session key exists + + # Generate the admin change URL + url = reverse("admin:a4_candy_users_user_change", args=[guest_user.pk]) + print(url) # Debugging: Print the URL + + # Authenticate as the admin user + apiclient.force_authenticate(user=admin) + + # Make a GET request to the admin change view + response = apiclient.get(url) + + # Debugging: Print the response status code and content + print(response.status_code) + print(response.content.decode()) + + # Assert the response status code is 200 (OK) + assert response.status_code == 200 From cad345260d86de3f19df947b8212f719a74939a0 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:57:53 +0100 Subject: [PATCH 12/16] Add access guest with admin test passes --- tests/factories.py | 1 + tests/users/test_views.py | 35 +++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/tests/factories.py b/tests/factories.py index fc1effa65..235ece929 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -23,6 +23,7 @@ class Meta: email = factory.Sequence(lambda n: "admin%d@liqd.net" % n) password = make_password("password") is_superuser = True + is_staff = True language = "en" diff --git a/tests/users/test_views.py b/tests/users/test_views.py index f53a12374..5d505ec3f 100644 --- a/tests/users/test_views.py +++ b/tests/users/test_views.py @@ -275,41 +275,48 @@ def test_profile(client, user): @pytest.mark.django_db -def test_admin_can_access_guest_user(admin, apiclient, logout_url): +def test_admin_can_access_guest_user(client, admin): # Create a guest user guest_user_creator = GuestUserCreator() guest_user = guest_user_creator.create_guest_user() assert is_guest_user(guest_user) - # Log out of the guest session (clear session and authentication) - apiclient.logout() # Clear authentication - if hasattr(apiclient, "session"): - apiclient.session.flush() # Clear the session + # Log out of any existing session (clear session and authentication) + client.logout() + + # Explicitly flush the session + if hasattr(client, "session"): + client.session.flush() # Assert that we're logged out # 1. Check authentication state by making a request and inspecting the user - response = apiclient.get("/some-public-endpoint/") # Use a public endpoint + response = client.get("/") # Use a public endpoint assert isinstance( response.wsgi_request.user, AnonymousUser ) # Ensure user is AnonymousUser # 2. Check session state - if hasattr(apiclient, "session"): - assert not apiclient.session.session_key # Ensure no session key exists + if hasattr(client, "session"): + assert not client.session.session_key # Ensure no session key exists # Generate the admin change URL url = reverse("admin:a4_candy_users_user_change", args=[guest_user.pk]) - print(url) # Debugging: Print the URL + print("Admin change URL:", url) # Debugging: Print the URL + + # Log in the admin user using Django's Client + client.force_login(admin) - # Authenticate as the admin user - apiclient.force_authenticate(user=admin) + # Debugging: Print the session key and authenticated user + print("Session key after login:", client.session.session_key) + print("Authenticated user:", client.session.get("_auth_user_id")) # Make a GET request to the admin change view - response = apiclient.get(url) + response = client.get(url) # Debugging: Print the response status code and content - print(response.status_code) - print(response.content.decode()) + print("Response status code:", response.status_code) + # print("Response URL (if redirect):", response.url) + print("Response content:", response.content.decode()) # Assert the response status code is 200 (OK) assert response.status_code == 200 From 83903687f6eaafb94a685c84dec3bf0176d3e0ad Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:59:22 +0100 Subject: [PATCH 13/16] Clean up code --- tests/users/test_views.py | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/tests/users/test_views.py b/tests/users/test_views.py index 5d505ec3f..e9be3b600 100644 --- a/tests/users/test_views.py +++ b/tests/users/test_views.py @@ -276,47 +276,22 @@ def test_profile(client, user): @pytest.mark.django_db def test_admin_can_access_guest_user(client, admin): - # Create a guest user guest_user_creator = GuestUserCreator() guest_user = guest_user_creator.create_guest_user() assert is_guest_user(guest_user) - # Log out of any existing session (clear session and authentication) client.logout() - # Explicitly flush the session if hasattr(client, "session"): client.session.flush() - # Assert that we're logged out - # 1. Check authentication state by making a request and inspecting the user - response = client.get("/") # Use a public endpoint - assert isinstance( - response.wsgi_request.user, AnonymousUser - ) # Ensure user is AnonymousUser + response = client.get("/") + assert isinstance(response.wsgi_request.user, AnonymousUser) - # 2. Check session state if hasattr(client, "session"): - assert not client.session.session_key # Ensure no session key exists + assert not client.session.session_key - # Generate the admin change URL url = reverse("admin:a4_candy_users_user_change", args=[guest_user.pk]) - print("Admin change URL:", url) # Debugging: Print the URL - - # Log in the admin user using Django's Client client.force_login(admin) - - # Debugging: Print the session key and authenticated user - print("Session key after login:", client.session.session_key) - print("Authenticated user:", client.session.get("_auth_user_id")) - - # Make a GET request to the admin change view response = client.get(url) - - # Debugging: Print the response status code and content - print("Response status code:", response.status_code) - # print("Response URL (if redirect):", response.url) - print("Response content:", response.content.decode()) - - # Assert the response status code is 200 (OK) assert response.status_code == 200 From c0dd8e7876f9629753ac22baaca2bccbdd1f424b Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Mon, 10 Mar 2025 12:13:51 +0100 Subject: [PATCH 14/16] Add move guest admin test to new file --- tests/users/test_user_admin.py | 34 +++++++++++++++++++++++++++++++ tests/users/test_views.py | 37 ---------------------------------- 2 files changed, 34 insertions(+), 37 deletions(-) create mode 100644 tests/users/test_user_admin.py diff --git a/tests/users/test_user_admin.py b/tests/users/test_user_admin.py new file mode 100644 index 000000000..096159197 --- /dev/null +++ b/tests/users/test_user_admin.py @@ -0,0 +1,34 @@ +import pytest +from django.contrib import auth +from django.contrib.auth.models import AnonymousUser +from django.urls import reverse +from guest_user.functions import get_guest_model +from guest_user.functions import is_guest_user + +from tests.helpers import GuestUserCreator + +User = auth.get_user_model() +GuestUser = get_guest_model() + + +@pytest.mark.django_db +def test_admin_can_access_guest_user(client, admin): + guest_user_creator = GuestUserCreator() + guest_user = guest_user_creator.create_guest_user() + assert is_guest_user(guest_user) + + client.logout() + + if hasattr(client, "session"): + client.session.flush() + + response = client.get("/") + assert isinstance(response.wsgi_request.user, AnonymousUser) + + if hasattr(client, "session"): + assert not client.session.session_key + + url = reverse("admin:a4_candy_users_user_change", args=[guest_user.pk]) + client.force_login(admin) + response = client.get(url) + assert response.status_code == 200 diff --git a/tests/users/test_views.py b/tests/users/test_views.py index e9be3b600..c6b99b5a6 100644 --- a/tests/users/test_views.py +++ b/tests/users/test_views.py @@ -3,29 +3,15 @@ import pytest from allauth.account.models import EmailAddress from django.contrib import auth -from django.contrib.auth.models import AnonymousUser from django.core import mail from django.test import override_settings from django.urls import reverse from django.utils import translation -# from django.test import Client -from guest_user.functions import get_guest_model -from guest_user.functions import is_guest_user - from adhocracy4.test.helpers import redirect_target from apps.users import models -from tests.helpers import GuestUserCreator - -# from django.contrib.sessions.backends.db import SessionStore -# from django.test import RequestFactory - -# from django.contrib.sessions.middleware import SessionMiddleware -# from django.contrib.auth.middleware import AuthenticationMiddleware -# from django.test import RequestFactory User = auth.get_user_model() -GuestUser = get_guest_model() @override_settings(LANGUAGE_CODE="de") @@ -272,26 +258,3 @@ def test_profile(client, user): response = client.get(url) assert response.status_code == 200 assert response.context["user"] == user - - -@pytest.mark.django_db -def test_admin_can_access_guest_user(client, admin): - guest_user_creator = GuestUserCreator() - guest_user = guest_user_creator.create_guest_user() - assert is_guest_user(guest_user) - - client.logout() - - if hasattr(client, "session"): - client.session.flush() - - response = client.get("/") - assert isinstance(response.wsgi_request.user, AnonymousUser) - - if hasattr(client, "session"): - assert not client.session.session_key - - url = reverse("admin:a4_candy_users_user_change", args=[guest_user.pk]) - client.force_login(admin) - response = client.get(url) - assert response.status_code == 200 From c1846b0f4805244caf2667b443142a8fb4b75c02 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Tue, 11 Mar 2025 13:25:01 +0100 Subject: [PATCH 15/16] Add refactor guest and signup form with TermsAndCaptchaMixin --- apps/users/forms.py | 71 +++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 47 deletions(-) diff --git a/apps/users/forms.py b/apps/users/forms.py index 4dde22acb..f3082659f 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -38,8 +38,26 @@ def __init__(self, *args, **kwargs): self.fields["password"].widget.attrs["autocomplete"] = "current-password" -class DefaultSignupForm(SignupForm): - terms_of_use = forms.BooleanField(label=_("Terms of use")) +class TermsAndCaptchaMixin: + def __init__(self, *args, **kwargs): + # First, call the parent class's __init__ to ensure self.fields is populated + super().__init__(*args, **kwargs) + + if "terms_of_use" not in self.fields: + self.fields["terms_of_use"] = forms.BooleanField(label=_("Terms of use")) + if "captcha" not in self.fields: + self.fields["captcha"] = CaptcheckCaptchaField(label=_("I am not a robot")) + + # Remove captcha if not configured + if not (hasattr(settings, "CAPTCHA_URL") and settings.CAPTCHA_URL): + del self.fields["captcha"] + else: + self.fields["captcha"].help_text = helpers.add_email_link_to_helptext( + self.fields["captcha"].help_text, CAPTCHA_HELP + ) + + +class DefaultSignupForm(TermsAndCaptchaMixin, SignupForm): get_newsletters = forms.BooleanField( label=_("I would like to receive further information"), help_text=_( @@ -48,7 +66,6 @@ class DefaultSignupForm(SignupForm): ), required=False, ) - captcha = CaptcheckCaptchaField(label=_("I am not a robot")) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -64,12 +81,6 @@ def __init__(self, *args, **kwargs): self.fields["email"].widget.attrs["autocomplete"] = "username" self.fields["password1"].widget.attrs["autocomplete"] = "new-password" self.fields["password2"].widget.attrs["autocomplete"] = "new-password" - if not (hasattr(settings, "CAPTCHA_URL") and settings.CAPTCHA_URL): - del self.fields["captcha"] - else: - self.fields["captcha"].help_text = helpers.add_email_link_to_helptext( - self.fields["captcha"].help_text, CAPTCHA_HELP - ) def save(self, request): user = super().save(request) @@ -80,48 +91,14 @@ def save(self, request): return user -# class GuestCreateForm(SignupForm): -# terms_of_use = forms.BooleanField(label=_("Terms of use")) -# captcha = CaptcheckCaptchaField(label=_("I am not a robot")) - -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# print("Initializing GuestCreateForm") # Debug statement -# del self.fields["username"] -# del self.fields["email"] -# del self.fields["password1"] -# del self.fields["password2"] -# if not (hasattr(settings, "CAPTCHA_URL") and settings.CAPTCHA_URL): -# del self.fields["captcha"] -# else: -# self.fields["captcha"].help_text = helpers.add_email_link_to_helptext( -# self.fields["captcha"].help_text, CAPTCHA_HELP -# ) +class GuestCreateForm(TermsAndCaptchaMixin, forms.Form): + pass -# def save(self, request): -# maybe_create_guest_user(request) - -# from django import forms -# from django.utils.translation import gettext_lazy as _ -# from captcheck.fields import CaptcheckCaptchaField -# from django.conf import settings - - -class GuestCreateForm(forms.Form): - terms_of_use = forms.BooleanField(label=_("Terms of use")) - captcha = CaptcheckCaptchaField(label=_("I am not a robot")) - # next = forms.CharField(widget=forms.HiddenInput, required=False) +class GuestConvertForm(DefaultSignupForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - - # Remove captcha if not configured - if not (hasattr(settings, "CAPTCHA_URL") and settings.CAPTCHA_URL): - del self.fields["captcha"] - else: - self.fields["captcha"].help_text = helpers.add_email_link_to_helptext( - self.fields["captcha"].help_text, CAPTCHA_HELP - ) + del self.fields["captcha"] class IgbceSignupForm(DefaultSignupForm): From c2d9b70a18e3ced1f2c65b8071f608041b00b024 Mon Sep 17 00:00:00 2001 From: Josh <195121232+partizipation@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:51:38 +0100 Subject: [PATCH 16/16] Add start guest convert --- adhocracy-plus/config/urls.py | 2 +- .../a4_candy_account/guest_convert.html | 26 +++++++ apps/account/urls.py | 5 ++ apps/account/views.py | 72 ++++++++++++++++++- apps/users/forms.py | 34 +++++++-- 5 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 apps/account/templates/a4_candy_account/guest_convert.html diff --git a/adhocracy-plus/config/urls.py b/adhocracy-plus/config/urls.py index 8ac31d047..9f5a304c5 100644 --- a/adhocracy-plus/config/urls.py +++ b/adhocracy-plus/config/urls.py @@ -96,7 +96,7 @@ re_path(r"^django-admin/", admin.site.urls), path("admin/", include("wagtail.admin.urls")), path("documents/", include(wagtaildocs_urls)), - path("convert/", include("guest_user.urls")), + # path("convert/", include("guest_user.urls")), path("accounts/guests/login", GuestCreateView.as_view(), name="guest_create"), path("accounts/", include("allauth.urls")), path("account/", include("apps.account.urls")), diff --git a/apps/account/templates/a4_candy_account/guest_convert.html b/apps/account/templates/a4_candy_account/guest_convert.html new file mode 100644 index 000000000..ebb558170 --- /dev/null +++ b/apps/account/templates/a4_candy_account/guest_convert.html @@ -0,0 +1,26 @@ +{% extends "a4_candy_account/account_dashboard.html" %} + +{% load i18n %} + +{% block title %}{% translate 'Your user profile' %} — {{ block.super }}{% endblock %} +{% block dashboard_content %} +

{% translate 'Convert to regular account' %}

+ + + {% csrf_token %} + + {% for field in form %} + {% if not field.name == 'get_notifications' and not field.name == 'get_newsletters' %} + {% include 'a4_candy_contrib/includes/form_field.html' with field=field %} + {% endif %} + {% endfor %} + + {% include 'a4_candy_contrib/includes/form_checkbox_field.html' with field=form.get_notifications %} + {% include 'a4_candy_contrib/includes/form_checkbox_field.html' with field=form.get_newsletters %} + +
+ +
+
+ {{ form.media }} +{% endblock %} diff --git a/apps/account/urls.py b/apps/account/urls.py index b0f982262..a19221c4a 100644 --- a/apps/account/urls.py +++ b/apps/account/urls.py @@ -15,4 +15,9 @@ views.OrganisationTermsOfUseUpdateView.as_view(), name="user_agreements", ), + path( + "convert/", + views.GuestConvertView.as_view(), + name="guest_convert", + ), ] diff --git a/apps/account/views.py b/apps/account/views.py index ae6f66232..aa194ab6a 100644 --- a/apps/account/views.py +++ b/apps/account/views.py @@ -4,14 +4,19 @@ from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import ValidationError from django.db import transaction -from django.shortcuts import get_object_or_404 +from django.urls import reverse +from django.shortcuts import get_object_or_404, redirect from django.utils.translation import gettext_lazy as _ from django.views import generic from django.views.generic.base import RedirectView from apps.users.models import User +from apps.users.forms import GuestConvertForm from apps.users.utils import set_session_language +from guest_user.views import ConvertFormView +from guest_user.models import Guest + from . import forms from .emails import AccountDeletionEmail @@ -46,6 +51,71 @@ def render_to_response(self, context, **response_kwargs): return response + # def dispatch(self, request, *args, **kwargs): + # if request.user.is_anonymous: + # return redirect(self.get_anonymous_redirect()) + + # if not is_guest_user(request.user): + # return redirect(self.get_user_redirect()) + + # return super().dispatch(request, *args, **kwargs) + + # def get_anonymous_redirect(self): + # """Return the URL to redirect anonymous users to.""" + # return self.anonymous_redirect or django_settings.LOGIN_URL + + # def get_user_redirect(self): + # """Return the URL to redirect regular users to.""" + # return self.user_redirect or django_settings.LOGIN_REDIRECT_URL + +# @guestusersonly decorator +class GuestConvertView(SuccessMessageMixin, ConvertFormView, generic.FormView): + template_name = "a4_candy_account/guest_convert.html" # TODO make template + # TODO do i still need to set this class, perhaps instead of + form_class = GuestConvertForm + success_message = _("Your guest account has been successfully converted to a regular account.") # TODO still works on redirect? + + # TODO add guest user checks, guest user delete + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['user'] = self.request.user # Pass the guest user to the form + # Ensure 'instance' is not passed + if 'instance' in kwargs: + del kwargs['instance'] + return kwargs + + def get_initial(self): + """Return the initial data to use for forms on this view.""" + print("got initial") + return super().get_initial() + + def form_valid(self, form): + # Convert the guest user to a regular user + user = form.save(self.request) + Guest.objects.filter(user=user).delete() + + # Optionally, you can log the user in again or perform other actions + return redirect(self.get_success_url()) + + # def form_valid(self, form): + # # Convert the guest user to a regular user using the GuestManager's convert method + # user = Guest.objects.convert(form) + + # # Optionally, you can log the user in again or perform other actions + # return redirect(self.get_success_url()) + + def get_success_url(self): + return reverse('account_profile') + + def render_to_response(self, context, **response_kwargs): + # Set the session language and cookie + # set_session_language(self.request.user.email, self.request.user.language) + response = super().render_to_response(context, **response_kwargs) + # response.set_cookie(settings.LANGUAGE_COOKIE_NAME, self.request.user.language) + return response + + class AccountDeletionView(LoginRequiredMixin, SuccessMessageMixin, generic.DeleteView): template_name = "a4_candy_account/account_deletion.html" form_class = forms.AccountDeletionForm diff --git a/apps/users/forms.py b/apps/users/forms.py index f3082659f..b7345865e 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -68,8 +68,20 @@ class DefaultSignupForm(TermsAndCaptchaMixin, SignupForm): ) def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user', None) # Accept an existing user instance super().__init__(*args, **kwargs) - print("Initializing default signup") # Debug statement + + # needs an extra check for guest here? or would be checked elsewhere before? + if self.user: + # Populate the form with the existing user's data + # self.fields['email'].initial = self.user.email + # self.fields['username'].initial = self.user.username + # self.fields['get_newsletters'].initial = self.user.get_newsletters + self.fields['email'].required = True + self.fields['username'].required = True + self.fields['password1'].required = True + self.fields['password2'].required = True + self.fields["username"].help_text = _( "Your username will appear publicly next to your posts." ) @@ -83,13 +95,25 @@ def __init__(self, *args, **kwargs): self.fields["password2"].widget.attrs["autocomplete"] = "new-password" def save(self, request): - user = super().save(request) - if user: + if self.user: + # Update the existing guest user + user = self.user + user.email = self.cleaned_data["email"] + user.username = self.cleaned_data["username"] user.get_newsletters = self.cleaned_data["get_newsletters"] - user.language = get_language() + # user.language = get_language() + user.set_password(self.cleaned_data["password1"]) # Set the new password + user.is_guest = False # Mark the user as a regular user user.save() return user - + else: + # Create a new user (original behavior) + user = super().save(request) + if user: + user.get_newsletters = self.cleaned_data["get_newsletters"] + user.language = get_language() + user.save() + return user class GuestCreateForm(TermsAndCaptchaMixin, forms.Form): pass