diff --git a/passbook/admin/urls.py b/passbook/admin/urls.py index 2133b1bce..7853ea20b 100644 --- a/passbook/admin/urls.py +++ b/passbook/admin/urls.py @@ -35,9 +35,10 @@ urlpatterns = [ path('providers//delete/', providers.ProviderDeleteView.as_view(), name='provider-delete'), # Invitations - path('invitations/', invitations.InviteListView.as_view(), name='invitations'), - path('invitations/create/', invitations.InviteCreateView.as_view(), name='invitation-create'), + path('invitations/', invitations.InvitationListView.as_view(), name='invitations'), + path('invitations/create/', + invitations.InvitationCreateView.as_view(), name='invitation-create'), path('invitations//delete/', - invitations.InviteDeleteView.as_view(), name='invitation-delete'), + invitations.InvitationDeleteView.as_view(), name='invitation-delete'), # path('api/v1/', include('passbook.admin.api.v1.urls')) ] diff --git a/passbook/admin/views/invitations.py b/passbook/admin/views/invitations.py index a34f47854..be441bcf2 100644 --- a/passbook/admin/views/invitations.py +++ b/passbook/admin/views/invitations.py @@ -1,38 +1,40 @@ -"""passbook Invite administration""" +"""passbook Invitation administration""" from django.contrib.messages.views import SuccessMessageMixin +from django.http import HttpResponseRedirect from django.urls import reverse_lazy from django.utils.translation import ugettext as _ -from django.views.generic import CreateView, DeleteView, ListView, UpdateView +from django.views.generic import CreateView, DeleteView, ListView from passbook.admin.mixins import AdminRequiredMixin -from passbook.core.forms.invitations import InviteForm -from passbook.core.models import Invite +from passbook.core.forms.invitations import InvitationForm +from passbook.core.models import Invitation -class InviteListView(AdminRequiredMixin, ListView): +class InvitationListView(AdminRequiredMixin, ListView): """Show list of all invitations""" - model = Invite + model = Invitation template_name = 'administration/invitation/list.html' -class InviteCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView): - """Create new Invite""" +class InvitationCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView): + """Create new Invitation""" template_name = 'generic/create.html' success_url = reverse_lazy('passbook_admin:invitations') - success_message = _('Successfully created Invite') - form_class = InviteForm + success_message = _('Successfully created Invitation') + form_class = InvitationForm - def get_initial(self): - return { - 'created_by': self.request.user - } + def form_valid(self, form): + obj = form.save(commit=False) + obj.created_by = self.request.user + obj.save() + return HttpResponseRedirect(self.success_url) -class InviteDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): +class InvitationDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): """Delete invitation""" - model = Invite + model = Invitation template_name = 'generic/delete.html' success_url = reverse_lazy('passbook_admin:invitations') - success_message = _('Successfully updated Invite') + success_message = _('Successfully updated Invitation') diff --git a/passbook/audit/migrations/0004_auto_20181210_1348.py b/passbook/audit/migrations/0004_auto_20181210_1348.py new file mode 100644 index 000000000..b1e151fc3 --- /dev/null +++ b/passbook/audit/migrations/0004_auto_20181210_1348.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2018-12-10 13:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('passbook_audit', '0003_auto_20181210_1213'), + ] + + operations = [ + migrations.AlterField( + model_name='auditentry', + name='action', + field=models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')]), + ), + ] diff --git a/passbook/audit/signals.py b/passbook/audit/signals.py index 7b7de0637..fa5f22777 100644 --- a/passbook/audit/signals.py +++ b/passbook/audit/signals.py @@ -25,12 +25,12 @@ def on_user_signed_up(sender, request, user, **kwargs): @receiver(invitation_created) def on_invitation_created(sender, request, invitation, **kwargs): - """Log Invite creation""" + """Log Invitation creation""" AuditEntry.create(AuditEntry.ACTION_INVITE_CREATED, request, invitation_uuid=invitation.uuid) @receiver(invitation_used) def on_invitation_used(sender, request, invitation, **kwargs): - """Log Invite usage""" + """Log Invitation usage""" AuditEntry.create(AuditEntry.ACTION_INVITE_USED, request, invitation_uuid=invitation.uuid) @receiver(user_login_failed) diff --git a/passbook/core/forms/invitations.py b/passbook/core/forms/invitations.py index d6118da6e..f9517d6e6 100644 --- a/passbook/core/forms/invitations.py +++ b/passbook/core/forms/invitations.py @@ -2,22 +2,21 @@ from django import forms -from passbook.core.models import Invite +from passbook.core.models import Invitation -class InviteForm(forms.ModelForm): - """InviteForm""" +class InvitationForm(forms.ModelForm): + """InvitationForm""" class Meta: - model = Invite - fields = ['created_by', 'expires', 'fixed_username', 'fixed_email'] + model = Invitation + fields = ['expires', 'fixed_username', 'fixed_email'] labels = { 'fixed_username': "Force user's username (optional)", 'fixed_email': "Force user's email (optional)", } widgets = { - 'created_by': forms.Select(attrs={'disabled': 'disabled'}), 'fixed_username': forms.TextInput(), 'fixed_email': forms.TextInput(), } diff --git a/passbook/core/migrations/0003_invite.py b/passbook/core/migrations/0003_invite.py index 87db8e406..5b18c87b2 100644 --- a/passbook/core/migrations/0003_invite.py +++ b/passbook/core/migrations/0003_invite.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Invite', + name='Invitation', fields=[ ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('expires', models.DateTimeField(blank=True, default=None, null=True)), @@ -24,7 +24,7 @@ class Migration(migrations.Migration): ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name': 'Invite', + 'verbose_name': 'Invitation', 'verbose_name_plural': 'Invitations', }, ), diff --git a/passbook/core/models.py b/passbook/core/models.py index d30db636e..df6deec01 100644 --- a/passbook/core/models.py +++ b/passbook/core/models.py @@ -8,6 +8,7 @@ from uuid import uuid4 import reversion from django.contrib.auth.models import AbstractUser from django.db import models +from django.urls import reverse_lazy from django.utils.translation import gettext as _ from model_utils.managers import InheritanceManager @@ -250,7 +251,7 @@ class DebugRule(Rule): verbose_name = _('Debug Rule') verbose_name_plural = _('Debug Rules') -class Invite(UUIDModel): +class Invitation(UUIDModel): """Single-use invitation link""" created_by = models.ForeignKey('User', on_delete=models.CASCADE) @@ -258,10 +259,15 @@ class Invite(UUIDModel): fixed_username = models.TextField(blank=True, default=None) fixed_email = models.TextField(blank=True, default=None) + @property + def link(self): + """Get link to use invitation""" + return reverse_lazy('passbook_core:auth-sign-up') + '?invitation=%s' % self.uuid + def __str__(self): - return "Invite %s created by %s" % (self.uuid, self.created_by) + return "Invitation %s created by %s" % (self.uuid, self.created_by) class Meta: - verbose_name = _('Invite') + verbose_name = _('Invitation') verbose_name_plural = _('Invitations') diff --git a/passbook/core/signals.py b/passbook/core/signals.py index b09d24036..5052dbc8a 100644 --- a/passbook/core/signals.py +++ b/passbook/core/signals.py @@ -4,7 +4,7 @@ from django.core.signals import Signal # from django.db.models.signals import post_save, pre_delete # from django.dispatch import receiver -# from passbook.core.models import Invite, User +# from passbook.core.models import Invitation, User user_signed_up = Signal(providing_args=['request', 'user']) # TODO: Send this signal in admin interface diff --git a/passbook/core/views/authentication.py b/passbook/core/views/authentication.py index a7883077f..fc3ad28fd 100644 --- a/passbook/core/views/authentication.py +++ b/passbook/core/views/authentication.py @@ -12,7 +12,7 @@ from django.views import View from django.views.generic import FormView from passbook.core.forms.authentication import LoginForm, SignUpForm -from passbook.core.models import Invite, User +from passbook.core.models import Invitation, User from passbook.core.signals import invitation_used, user_signed_up from passbook.lib.config import CONFIG @@ -118,7 +118,7 @@ class SignUpView(UserPassesTestMixin, FormView): template_name = 'login/form.html' form_class = SignUpForm success_url = '.' - # Invite insatnce, if invitation link was used + # Invitation insatnce, if invitation link was used _invitation = None # Instance of newly created user _user = None @@ -134,7 +134,7 @@ class SignUpView(UserPassesTestMixin, FormView): """Check if sign-up is enabled or invitation link given""" allowed = False if 'invitation' in request.GET: - invitations = Invite.objects.filter(uuid=request.GET.get('invitation')) + invitations = Invitation.objects.filter(uuid=request.GET.get('invitation')) allowed = invitations.exists() if allowed: self._invitation = invitations.first() @@ -145,6 +145,16 @@ class SignUpView(UserPassesTestMixin, FormView): return redirect(reverse('passbook_core:auth-login')) return super().dispatch(request, *args, **kwargs) + def get_initial(self): + if self._invitation: + initial = {} + if self._invitation.fixed_username: + initial['username'] = self._invitation.fixed_username + if self._invitation.fixed_email: + initial['e-mail'] = self._invitation.fixed_email + return initial + return super().get_initial() + def get_context_data(self, **kwargs): kwargs['config'] = CONFIG.get('passbook') kwargs['is_login'] = True