Skip to content

Commit ef376e0

Browse files
authored
Merge pull request #338 from bruecksen/175-genussrechtler
Add option to automatically add a user to a group on signup
2 parents aa0e686 + ffaa309 commit ef376e0

File tree

17 files changed

+205
-43
lines changed

17 files changed

+205
-43
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ typings/
150150

151151

152152
### VisualStudioCode template
153+
.vscode/
153154
.vscode/*
154155
!.vscode/settings.json
155156
!.vscode/tasks.json

bakeup/shop/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
path(_("order-templates/"), view=CustomerOrderTemplateListView.as_view(), name="order-template-list"),
2525
path("profile/", view=shop_user_update_view, name="user-profile"),
2626
path(_("login/"), view=LoginView.as_view(), name="login"),
27+
path(_("signup/<slug:token>/"), view=SignupView.as_view(), name="signup"),
2728
path(_("signup/"), view=SignupView.as_view(), name="signup"),
2829
path(_("account/closure/"), view=UserProfileDeleteView.as_view(), name="account-closure"),
2930
path("api/production-day-abo-products/<int:pk>/", view=production_day_abo_products, name="production-day-abo-products")

bakeup/static/js/project.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bakeup/templates/account/signup.html

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@
77
{% block page_content %}
88
<div class="container h-100">
99
<div class="row h-100 justify-content-center align-items-center">
10-
<div class="col-12 col-md-6">
10+
<div class="col-12 col-md-7">
1111
<div class="card">
1212
<div class="card-body tab-content min-height-500">
1313
<div class="d-flex justify-content-center">
1414
{% if settings.pages.BrandSettings.logo %}
1515
{% include "includes/logo.html" with logo_size='large' client_logo=settings.pages.BrandSettings.logo %}
1616
{% endif %}
1717
</div>
18-
<h2 class="my-3 fw-bold">{% trans "Sign Up" %}</h2>
18+
<h2 class="my-3 fw-bold">{% trans "Sign Up" %}
19+
{% if group %}
20+
{% trans "as" %} {{ group }}
21+
{% endif %}
22+
</h2>
1923
{% url 'shop:login' as login_url %}
2024
<p>{% blocktrans %}Already have an account? Then please <a href="{{ login_url }}">sign in</a>.{% endblocktrans %}</p>
2125

22-
<form class="signup" id="signup_form" method="post" action="{% url 'account_signup' %}">
26+
<form class="signup" id="signup_form" method="post">
2327
{% csrf_token %}
2428
{% crispy form %}
2529
{% if redirect_field_value %}

bakeup/users/forms.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from crispy_forms.layout import Layout, Submit, Row, Column, HTML
1010

1111
from allauth.utils import set_form_field_order
12-
from allauth.account.forms import SignupForm as _SignupForm
12+
from allauth.account.forms import SignupForm as _SignupForm, LoginForm as _LoginForm
1313

1414
from bakeup.shop.models import PointOfSale
1515

@@ -105,9 +105,22 @@ def save(self, request):
105105

106106

107107
class SignupForm(UserFormMixin, _SignupForm):
108-
pass
108+
email = forms.EmailField(
109+
widget=forms.TextInput(
110+
attrs={
111+
"type": "email",
112+
"placeholder": _("E-mail address"),
113+
"autocomplete": "email",
114+
"autofocus": True
115+
}
116+
)
117+
)
109118

110119

111120
class UserProfileForm(UserFormMixin, forms.Form):
112121
pass
113122

123+
class LoginForm(_LoginForm):
124+
def __init__(self, *args, **kwargs):
125+
super().__init__(*args, **kwargs)
126+
self.fields['login'].widget.attrs["autofocus"] = True
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 3.2.12 on 2023-09-20 08:23
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('auth', '0012_alter_user_first_name_max_length'),
11+
('users', '0004_alter_token_ttl'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='GroupToken',
17+
fields=[
18+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('token', models.CharField(max_length=64, unique=True)),
20+
('ttl', models.IntegerField(default=30, help_text='Days till token expires')),
21+
('created_at', models.DateTimeField(auto_now_add=True)),
22+
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='token', to='auth.group')),
23+
],
24+
options={
25+
'abstract': False,
26+
},
27+
),
28+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated by Django 3.2.12 on 2022-10-21 08:53
2+
import random
3+
import string
4+
5+
from django.conf import settings
6+
from django.db import migrations
7+
8+
def generate_token():
9+
alphabet = string.ascii_lowercase + string.digits
10+
return ''.join(random.choices(alphabet, k=8))
11+
12+
def make_tokens(apps, schema_editor):
13+
Group = apps.get_model('auth', 'Group')
14+
GroupToken = apps.get_model('users', 'GroupToken')
15+
tokens = [GroupToken(group=group, token=generate_token(), ttl=30) for group in Group.objects.filter(token__isnull=True)]
16+
GroupToken.objects.bulk_create(tokens)
17+
18+
class Migration(migrations.Migration):
19+
20+
dependencies = [
21+
('users', '0005_grouptoken'),
22+
]
23+
24+
operations = [
25+
migrations.RunPython(make_tokens),
26+
]

bakeup/users/models.py

+35-10
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,17 @@ def short_name(self):
4242
return self.first_name
4343

4444

45-
46-
class Token(models.Model):
45+
class AbstractToken(models.Model):
4746
"""Authentication token for user model"""
4847

4948
# Secret string
5049
token = models.CharField(max_length=64, unique=True)
5150
# Time to live - number of days until token expiration
5251
ttl = models.IntegerField(default=30, help_text="Days till token expires")
53-
user = models.OneToOneField(
54-
User,
55-
related_name='token',
56-
on_delete=models.CASCADE
57-
)
5852
created_at = models.DateTimeField(auto_now_add=True)
5953

60-
def __str__(self):
61-
return "Token: {}".format(self.user)
54+
class Meta:
55+
abstract = True
6256

6357
@classmethod
6458
def generate_token(cls):
@@ -89,4 +83,35 @@ def generate_qr_code(self, request):
8983
stream = BytesIO()
9084
img.save(stream)
9185
html = "<div style='background-color: white;display: inline-block;'>{}</div>".format(stream.getvalue().decode())
92-
return html
86+
return html
87+
88+
89+
90+
class Token(AbstractToken):
91+
"""Authentication token for user model"""
92+
user = models.OneToOneField(
93+
User,
94+
related_name='token',
95+
on_delete=models.CASCADE
96+
)
97+
98+
def __str__(self):
99+
return "Token: {}".format(self.user)
100+
101+
def get_full_url(self, request):
102+
return request.tenant.reverse(request, 'users:login-token', token=self.token)
103+
104+
105+
class GroupToken(AbstractToken):
106+
"""Authentication token for user model"""
107+
group = models.OneToOneField(
108+
'auth.Group',
109+
related_name='token',
110+
on_delete=models.CASCADE
111+
)
112+
113+
def __str__(self):
114+
return "Token: {}".format(self.group)
115+
116+
def get_full_url(self, request):
117+
return request.tenant.reverse(request, 'shop:signup', token=self.token)

bakeup/users/signals.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from django.db.models.signals import post_save
22
from django.dispatch import receiver
3+
from django.contrib.auth.models import Group
34

45
from allauth.account.models import EmailAddress
56
from allauth.account.signals import email_confirmed
67

78
from bakeup.shop.models import Customer, PointOfSale
8-
from bakeup.users.models import User, Token
9+
from bakeup.users.models import User, Token, GroupToken
910

1011

1112
@receiver(post_save, sender=User)
@@ -17,6 +18,15 @@ def create_user_token(sender, instance, created, **kwargs):
1718
)
1819

1920

21+
@receiver(post_save, sender=Group)
22+
def create_group_token(sender, instance, created, **kwargs):
23+
if not hasattr(instance, 'token'):
24+
GroupToken.objects.create(
25+
group=instance,
26+
token=GroupToken.generate_token(),
27+
)
28+
29+
2030
@receiver(post_save, sender=User)
2131
def create_user_customer(sender, instance, created, **kwargs):
2232
if not hasattr(instance, 'customer'):

bakeup/users/views.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from typing import Any
2+
from django import http
13
from django.core.mail import send_mail
24
from django.contrib.auth import logout
35
from django.contrib import messages
46
from django.contrib.auth import get_user_model, login
7+
from django.contrib.auth.models import Group
58
from django.contrib.auth.mixins import LoginRequiredMixin
69
from django.contrib.messages.views import SuccessMessageMixin
710
from django.urls import reverse, resolve
@@ -69,12 +72,33 @@ def form_valid(self, form):
6972

7073

7174
class SignupView(_SignupView):
75+
group = None
76+
77+
def setup(self, request, *args, **kwargs):
78+
token = kwargs.get('token', None)
79+
if token and Group.objects.filter(token__token=token).exists():
80+
self.group = Group.objects.get(token__token=token)
81+
return super().setup(request, *args, **kwargs)
82+
7283
def get_form_kwargs(self):
7384
kwargs = super().get_form_kwargs()
7485
kwargs.update({
75-
'request': self.request
86+
'request': self.request,
7687
})
7788
return kwargs
89+
90+
def get_context_data(self, **kwargs):
91+
context_data = super().get_context_data(**kwargs)
92+
context_data['group'] = self.group
93+
return context_data
94+
95+
def form_valid(self, form):
96+
response = super().form_valid(form)
97+
if self.group and self.user:
98+
# add user to group if group available
99+
self.user.groups.add(self.group)
100+
return response
101+
78102

79103

80104

bakeup/workshop/tables.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,13 @@ class Meta:
107107

108108

109109
class GroupTable(tables.Table):
110+
token = tables.TemplateColumn('{% load workshop_tags %}{% token_url record.token %}', verbose_name=_('Signup url'))
110111
user_count = tables.Column(empty_values=(), verbose_name=_('Users'))
111112
actions = tables.TemplateColumn(template_name='tables/group_actions_column.html', verbose_name='')
112113

113114
class Meta:
114115
model = Group
115-
fields = ("name",)
116+
fields = ("name", "token")
116117

117118
def render_user_count(self, value, record):
118119
return record.user_set.count()

bakeup/workshop/templatetags/workshop_tags.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,10 @@ def ordered_quantity(order, product):
2828
position = order.positions.filter(
2929
Q(product=product) | Q(product__product_template=product),
3030
).first()
31-
return position and position.quantity or 0
31+
return position and position.quantity or 0
32+
33+
34+
@register.simple_tag(takes_context=True)
35+
def token_url(context, token):
36+
if token:
37+
return token.token_url(context.get('request'))

config/settings/base.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,10 @@
326326
ACCOUNT_UNIQUE_EMAIL = True
327327

328328
ACCOUNT_ADAPTER = 'bakeup.users.allauth.AccountAdapter'
329-
ACCOUNT_FORMS = {'signup': 'bakeup.users.forms.SignupForm'}
329+
ACCOUNT_FORMS = {
330+
'signup': 'bakeup.users.forms.SignupForm',
331+
'login': 'bakeup.users.forms.LoginForm',
332+
}
330333
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
331334
# ACCOUNT_EMAIL_VERIFICATION = "mandatory"
332335
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True

locale/de_DE/LC_MESSAGES/django.mo

71 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)