Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/invite with digid #231

Merged
merged 9 commits into from
May 24, 2022
2 changes: 1 addition & 1 deletion src/open_inwoner/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,6 @@ class MessageAdmin(PrivateMediaMixin, admin.ModelAdmin):

@admin.register(Invite)
class InviteAdmin(admin.ModelAdmin):
list_display = ("inviter", "invitee", "accepted", "created_on")
list_display = ("inviter", "invitee_email", "invitee", "accepted", "created_on")
list_filter = ("inviter", "invitee")
readonly_fields = ("key",)
38 changes: 13 additions & 25 deletions src/open_inwoner/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,17 @@ class Meta:
"invite",
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def clean_email(self):
email = self.cleaned_data["email"]

if self.initial.get("invite") or self.data.get("invite"):
self.fields["email"].widget.attrs["readonly"] = "readonly"
del self.fields["email"].widget.attrs["autofocus"]
existing_user = User.objects.filter(email=email).first()
if not existing_user:
return email

def save(self, commit=True):
self.instance.is_active = True

user = super().save(commit)

# if there is invite - create reverse contact relations
invite = self.cleaned_data.get("invite")
if invite:
inviter = invite.inviter
Contact.objects.create(
first_name=inviter.first_name,
last_name=inviter.last_name,
email=inviter.email,
contact_user=inviter,
created_by=user,
)
return user
if existing_user.is_active:
raise ValidationError(_("The user with this email already exists"))

raise ValidationError(_("This user has been deactivated"))


class UserForm(forms.ModelForm):
Expand Down Expand Up @@ -111,9 +98,10 @@ def save(self, commit=True):
self.instance.created_by = self.user

if not self.instance.pk and self.instance.email:
self.instance.contact_user, created = User.objects.get_or_create(
email=self.instance.email, defaults={"is_active": False}
)
contact_user = User.objects.filter(email=self.instance.email).first()
if contact_user:
self.instance.contact_user = contact_user

return super().save(commit=commit)


Expand Down
38 changes: 38 additions & 0 deletions src/open_inwoner/accounts/migrations/0040_auto_20220518_1456.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 3.2.13 on 2022-05-18 12:56

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("accounts", "0039_remove_contact_type"),
]

operations = [
migrations.AddField(
model_name="invite",
name="invitee_email",
field=models.EmailField(
help_text="The email used to send the invite",
max_length=254,
null=True,
verbose_name="Invitee email",
),
),
migrations.AlterField(
model_name="invite",
name="invitee",
field=models.ForeignKey(
blank=True,
help_text="User who received the invite",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="received_invites",
to=settings.AUTH_USER_MODEL,
verbose_name="Invitee",
),
),
]
16 changes: 16 additions & 0 deletions src/open_inwoner/accounts/migrations/0041_migrate_invite_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db import migrations


def migrate_invite_email(apps, _):
Invite = apps.get_model("accounts", "Invite")

for invite in Invite.objects.select_related("invitee").all():
invite.invitee_email = invite.invitee.email
invite.save()


class Migration(migrations.Migration):

dependencies = [("accounts", "0040_auto_20220518_1456")]

operations = [migrations.RunPython(migrate_invite_email, migrations.RunPython.noop)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.13 on 2022-05-18 13:06

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("accounts", "0041_migrate_invite_email"),
]

operations = [
migrations.AlterField(
model_name="invite",
name="invitee_email",
field=models.EmailField(
help_text="The email used to send the invite",
max_length=254,
verbose_name="Invitee email",
),
),
]
12 changes: 9 additions & 3 deletions src/open_inwoner/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,10 +569,16 @@ class Invite(models.Model):
invitee = models.ForeignKey(
User,
verbose_name=_("Invitee"),
on_delete=models.CASCADE,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="received_invites",
help_text=_("User who received the invite"),
)
invitee_email = models.EmailField(
verbose_name=_("Invitee email"),
help_text=_("The email used to send the invite"),
)
contact = models.ForeignKey(
Contact,
verbose_name=_("Contact"),
Expand Down Expand Up @@ -615,11 +621,11 @@ def send(self, request=None):
template = find_template("invite")
context = {
"inviter_name": self.inviter.get_full_name(),
"email": self.invitee.email,
"email": self.invitee_email,
"invite_link": url,
}

return template.send_email([self.invitee.email], context)
return template.send_email([self.invitee_email], context)

def get_absolute_url(self) -> str:
return reverse("accounts:invite_accept", kwargs={"key": self.key})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
{% extends 'master.html' %}
{% load form_tags %}
{% load i18n static form_tags card_tags logo_tags link_tags %}

{% block content %}
{% form form_object=form method="POST" id="registration-form" submit_text=_('Registreren') %}

{% render_card direction='horizontal' tinted=True %}
{% static 'accounts/digid_logo.svg' as digid_logo_src %}
{% logo src=digid_logo_src alt=_('Inloggen met DigiD') height="40" href='digid:login' %}
{% link bold=True href=digit_url text=_('Registreren met DigiD') secondary=True icon='arrow_forward' %}
{% endrender_card %}

{% if login_allow_registration %}
{% form form_object=form method="POST" id="registration-form" submit_text=_('Registreren') %}
{% endif %}

{% endblock content %}
2 changes: 1 addition & 1 deletion src/open_inwoner/accounts/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@ class Meta:

contact = factory.SubFactory(ContactFactory)
inviter = factory.LazyAttribute(lambda o: o.contact.created_by)
invitee = factory.LazyAttribute(lambda o: o.contact.contact_user)
invitee_email = factory.LazyAttribute(lambda o: o.contact.email)
128 changes: 105 additions & 23 deletions src/open_inwoner/accounts/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
from django.utils.translation import gettext as _

from django_webtest import WebTest
from furl import furl

from open_inwoner.configurations.models import SiteConfiguration

from ..models import User
from .factories import InviteFactory, UserFactory
from .factories import ContactFactory, InviteFactory, UserFactory


class TestRegistrationFunctionality(WebTest):
Expand All @@ -18,6 +19,10 @@ def setUp(self):
# Create a User instance that's not saved
self.user = UserFactory.build()

self.config = SiteConfiguration.get_solo()
self.config.login_allow_registration = True
self.config.save()

def test_registration_succeeds_with_right_user_input(self):
register_page = self.app.get(reverse("django_registration_register"))
form = register_page.forms["registration-form"]
Expand Down Expand Up @@ -69,28 +74,24 @@ def test_registration_inactive_user(self):

response = form.submit()

self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("django_registration_complete"))

inactive_user.refresh_from_db()

self.assertTrue(inactive_user.is_active)
self.assertEqual(inactive_user.first_name, "John")
self.assertEqual(inactive_user.last_name, "Smith")
self.assertEqual(inactive_user.contacts.count(), 0)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.context["errors"].as_text(), "* This user has been deactivated"
)

def test_registration_with_invite(self):
invite = InviteFactory.create(
contact__created_by__email=self.user.email,
contact__contact_user__is_active=False,
)
invitee = invite.invitee
email = self.user.email
contact = ContactFactory.create(email=email, contact_user=None)
invite = InviteFactory.create(contact=contact, invitee=None)
self.assertFalse(User.objects.filter(email=email).exists())

register_page = self.app.get(f"{self.url}?invite={invite.key}")
form = register_page.forms["registration-form"]

# check that email is prefilled and read-only
self.assertEqual(form["email"].value, invitee.email)
self.assertEqual(form["email"].attrs.get("readonly"), "readonly")
# check that fields are prefilled with invite data
self.assertEqual(form["email"].value, email)
self.assertEqual(form["first_name"].value, contact.first_name)
self.assertEqual(form["last_name"].value, contact.last_name)

form["password1"] = "somepassword"
form["password2"] = "somepassword"
Expand All @@ -99,14 +100,95 @@ def test_registration_with_invite(self):

self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("django_registration_complete"))
self.assertTrue(User.objects.filter(email=email).exists())

user = User.objects.get(email=email)
contact.refresh_from_db()
invite.refresh_from_db()

self.assertEqual(user.first_name, contact.first_name)
self.assertEqual(user.last_name, contact.last_name)
self.assertEqual(contact.contact_user, user)
self.assertEqual(invite.invitee, user)

# reverse contact checks
self.assertEqual(user.contacts.count(), 1)
reverse_contact = user.contacts.get()
self.assertEqual(reverse_contact.contact_user, contact.created_by)
self.assertEqual(reverse_contact.email, contact.created_by.email)
self.assertEqual(reverse_contact.first_name, contact.created_by.first_name)
self.assertEqual(reverse_contact.last_name, contact.created_by.last_name)

def test_registration_active_user(self):
"""the user should be redirected to the registration complete page"""

user = UserFactory.create()

get_response = self.app.get(self.url, user=user)

invitee.refresh_from_db()
self.assertEqual(get_response.status_code, 302)
self.assertEqual(get_response.url, reverse("django_registration_complete"))

self.assertTrue(invitee.is_active)
self.assertEqual(invitee.contacts.count(), 1)
def test_registration_active_user_with_invite(self):
"""
the user should be redirected to the registration complete page
and the invite should be updated
"""

contact = invitee.contacts.get()
self.assertEqual(contact.contact_user, invite.inviter)
user = UserFactory.create()
contact = ContactFactory.create(email=user.email, contact_user=None)
invite = InviteFactory.create(contact=contact, invitee=None)
self.assertEqual(user.contacts.count(), 0)

get_response = self.app.get(f"{self.url}?invite={invite.key}", user=user)

self.assertEqual(get_response.status_code, 302)
self.assertEqual(get_response.url, reverse("django_registration_complete"))

contact.refresh_from_db()
invite.refresh_from_db()
self.assertEqual(contact.contact_user, user)
self.assertEqual(invite.invitee, user)

# reverse contact was created
self.assertEqual(user.contacts.count(), 1)
reverse_contact = user.contacts.get()
self.assertEqual(reverse_contact.contact_user, contact.created_by)
self.assertEqual(reverse_contact.email, contact.created_by.email)
self.assertEqual(reverse_contact.first_name, contact.created_by.first_name)
self.assertEqual(reverse_contact.last_name, contact.created_by.last_name)


class TestRegistrationDigid(WebTest):
url = reverse_lazy("django_registration_register")

def test_registration_page_only_digid(self):
get_response = self.app.get(self.url)

self.assertEqual(get_response.status_code, 200)
self.assertIsNone(get_response.html.find(id="registration-form"))

digid_tag = get_response.html.find("a", title="Registreren met DigiD")
self.assertIsNotNone(digid_tag)
self.assertEqual(
digid_tag.attrs["href"],
furl(reverse("digid:login")).add({"next": str(self.url)}).url,
)

def test_registration_page_only_digid_with_invite(self):
invite = InviteFactory.create()
url = f"{self.url}?invite={invite.key}"

get_response = self.app.get(url)

self.assertEqual(get_response.status_code, 200)
self.assertIsNone(get_response.html.find(id="registration-form"))

digid_tag = get_response.html.find("a", title="Registreren met DigiD")
self.assertIsNotNone(digid_tag)
self.assertEqual(
digid_tag.attrs["href"], furl(reverse("digid:login")).add({"next": url}).url
)


class TestLoginLogoutFunctionality(WebTest):
Expand Down
11 changes: 4 additions & 7 deletions src/open_inwoner/accounts/tests/test_contact_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,23 +156,20 @@ def test_contact_create_send_invite(self):
self.assertEqual(contact.last_name, "Smith")
self.assertEqual(contact.email, "john@smith.nl")

# check that the contact was created
contact_user = contact.contact_user
self.assertIsNotNone(contact_user)
self.assertEqual(contact_user.email, "john@smith.nl")
self.assertFalse(contact_user.is_active)
# check that the contact user was not created
self.assertIsNone(contact.contact_user)

# check that the invite was created
self.assertEqual(contact.invites.count(), 1)
invite = contact.invites.get()
self.assertEqual(invite.inviter, self.user)
self.assertEqual(invite.invitee, contact_user)
self.assertEqual(invite.invitee_email, contact.email)

# check that the invite was sent
self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
self.assertEqual(email.subject, "Uitnodiging voor Open Inwoner Platform")
self.assertEqual(email.to, [contact_user.email])
self.assertEqual(email.to, [invite.invitee_email])
invite_url = f"http://testserver{invite.get_absolute_url()}"
body = email.alternatives[0][0] # html version of the email body
self.assertIn(invite_url, body)
Expand Down
Loading