🚀 first commit
This commit is contained in:
0
src/users/__init__.py
Normal file
0
src/users/__init__.py
Normal file
41
src/users/admin.py
Normal file
41
src/users/admin.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
|
||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||
|
||||
from users import models
|
||||
|
||||
|
||||
class MyUserChangeForm(UserChangeForm):
|
||||
class Meta(UserChangeForm.Meta):
|
||||
model = models.User
|
||||
|
||||
|
||||
class MyUserCreationForm(UserCreationForm):
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = models.User
|
||||
|
||||
|
||||
@admin.register(models.User)
|
||||
class UserAdmin(AuthUserAdmin):
|
||||
form = MyUserChangeForm
|
||||
add_form = MyUserCreationForm
|
||||
list_display = ('username', 'first_name', 'last_name',
|
||||
'email', 'uid', )
|
||||
list_display_links = ('username',)
|
||||
fieldsets = AuthUserAdmin.fieldsets + (
|
||||
(None, {'fields': ('uid', )}),
|
||||
)
|
||||
|
||||
|
||||
@admin.register(models.ConfirmationCode)
|
||||
class ConfirmationCodeAdmin(admin.ModelAdmin):
|
||||
list_display = ('code', 'email', 'registration_id')
|
||||
search_fields = ('code', 'email')
|
||||
list_filter = ('registration_id', )
|
||||
list_display_links = ('code',)
|
||||
|
||||
|
||||
@admin.register(models.SecurityCode)
|
||||
class SecurityCodeAdmin(admin.ModelAdmin):
|
||||
list_display = ('email', 'code')
|
||||
search_fields = ('code', 'email')
|
||||
6
src/users/apps.py
Normal file
6
src/users/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'users'
|
||||
15
src/users/auth.py
Normal file
15
src/users/auth.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
|
||||
class EmailBackend(ModelBackend):
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
UserModel = get_user_model()
|
||||
try:
|
||||
user = UserModel.objects.get(email=username)
|
||||
except UserModel.DoesNotExist:
|
||||
return None
|
||||
else:
|
||||
if user.check_password(password):
|
||||
return user
|
||||
return None
|
||||
172
src/users/forms.py
Normal file
172
src/users/forms.py
Normal file
@@ -0,0 +1,172 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Div, HTML, Layout, Fieldset, Submit, Field
|
||||
from crispy_forms.bootstrap import FormActions, Tab, TabHolder
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
class LoginForm(AuthenticationForm):
|
||||
email = forms.EmailField(widget=forms.TextInput(attrs={'autofocus': True}))
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('email', 'password')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LoginForm, self).__init__(*args, **kwargs)
|
||||
self.fields.pop('username', None)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(LoginForm, self).clean()
|
||||
if 'username' in cleaned_data:
|
||||
del cleaned_data['username']
|
||||
return cleaned_data
|
||||
|
||||
def get_user(self):
|
||||
try:
|
||||
user = User.objects.get(email=self.cleaned_data.get('email'))
|
||||
|
||||
# check if the user is active
|
||||
if not user.is_active:
|
||||
raise forms.ValidationError("User is not active")
|
||||
# check password
|
||||
if not user.check_password(self.cleaned_data.get('password')):
|
||||
raise forms.ValidationError("Invalid password")
|
||||
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class UserRegistrationForm(forms.ModelForm):
|
||||
password1 = forms.CharField(label=_('Password'), widget=forms.PasswordInput)
|
||||
password2 = forms.CharField(label=_('Password confirmation'), widget=forms.PasswordInput)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('first_name', 'last_name', 'email', 'password1', 'password2')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UserRegistrationForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.form_method = 'post'
|
||||
self.helper.form_action = 'submit_survey'
|
||||
self.helper.add_input(Submit('submit', 'Submit', css_class='btn btn-primary d-block mx-auto'))
|
||||
self.fields['first_name'].required = True
|
||||
self.fields['last_name'].required = True
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Fieldset(
|
||||
'',
|
||||
Div(
|
||||
Div('first_name', css_class="col-6"),
|
||||
Div('last_name', css_class="col-6"),
|
||||
css_class="row"
|
||||
),
|
||||
Div(
|
||||
Div('email', 'password1', 'password2',
|
||||
css_class="col-12"),
|
||||
css_class="row"
|
||||
),
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ChangePasswordForm(forms.Form):
|
||||
password = forms.CharField(label=_('Old password'), widget=forms.PasswordInput)
|
||||
password1 = forms.CharField(label=_('Password'), widget=forms.PasswordInput)
|
||||
password2 = forms.CharField(label=_('Password confirmation'), widget=forms.PasswordInput)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ChangePasswordForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.form_method = 'post'
|
||||
self.helper.form_action = 'submit_survey'
|
||||
self.helper.add_input(Submit('submit', 'Submit', css_class='btn btn-primary d-block mx-auto'))
|
||||
|
||||
self.fields['password'].required = True
|
||||
self.fields['password1'].required = True
|
||||
self.fields['password2'].required = True
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Fieldset(
|
||||
'',
|
||||
Div(
|
||||
Div('password', 'password1', 'password2',
|
||||
css_class="col-12"),
|
||||
css_class="row"
|
||||
),
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ResetPasswordForm(forms.Form):
|
||||
email = forms.CharField(widget=forms.HiddenInput())
|
||||
reset_code = forms.CharField(widget=forms.HiddenInput())
|
||||
password1 = forms.CharField(label=_('Password'), widget=forms.PasswordInput)
|
||||
password2 = forms.CharField(label=_('Password confirmation'), widget=forms.PasswordInput)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ResetPasswordForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.form_method = 'post'
|
||||
self.helper.form_action = 'submit_survey'
|
||||
self.helper.add_input(Submit('submit', 'Submit', css_class='btn btn-primary d-block mx-auto'))
|
||||
|
||||
self.fields['email'].required = True
|
||||
self.fields['reset_code'].required = True
|
||||
self.fields['password1'].required = True
|
||||
self.fields['password2'].required = True
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Fieldset(
|
||||
'',
|
||||
Div(
|
||||
Div('email', 'reset_code', 'password1', 'password2',
|
||||
css_class="col-12"),
|
||||
css_class="row"
|
||||
),
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
class ForgotPasswordForm(forms.Form):
|
||||
email = forms.EmailField(label=_('Email'), widget=forms.EmailInput)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ForgotPasswordForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.form_method = 'post'
|
||||
self.helper.form_action = 'submit_survey'
|
||||
self.helper.add_input(Submit('submit', 'Submit', css_class='btn btn-primary d-block mx-auto'))
|
||||
|
||||
self.fields['email'].required = True
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Fieldset(
|
||||
'',
|
||||
Div(
|
||||
Div('email', css_class="col-12"),
|
||||
css_class="row"
|
||||
),
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
def get_user(self):
|
||||
try:
|
||||
user = User.objects.get(
|
||||
email=self.cleaned_data.get('email'),
|
||||
is_active=True
|
||||
)
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
74
src/users/migrations/0001_initial.py
Normal file
74
src/users/migrations/0001_initial.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-05 10:45
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ConfirmationCode',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='email address')),
|
||||
('code', models.CharField(max_length=50, unique=True, verbose_name='Code')),
|
||||
('registration_id', models.IntegerField(blank=True, null=True, verbose_name='Registration ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Confirmation code',
|
||||
'verbose_name_plural': 'Confirmation codes',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SecurityCode',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, unique=True, verbose_name='email address')),
|
||||
('code', models.CharField(max_length=10, unique=True, verbose_name='Code')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Security code',
|
||||
'verbose_name_plural': 'Security codes',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
|
||||
('uid', models.UUIDField(verbose_name='UUID')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'User',
|
||||
'verbose_name_plural': 'Users',
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
32
src/users/migrations/0002_add_superuser.py
Normal file
32
src/users/migrations/0002_add_superuser.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Generated by Django 4.0.5 on 2022-10-04 19:19
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_default_superuser(apps, schema_editor):
|
||||
User = apps.get_model("users", "User")
|
||||
|
||||
try:
|
||||
new_superuser = User.objects.create_superuser(
|
||||
uid="50ca4e0b-8f5a-48af-a63c-426b3adce3d0",
|
||||
username="admin@admin.cz",
|
||||
password="Heslo1234",
|
||||
email="admin@admin.cz")
|
||||
|
||||
new_superuser.save()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_superuser),
|
||||
]
|
||||
0
src/users/migrations/__init__.py
Normal file
0
src/users/migrations/__init__.py
Normal file
88
src/users/models.py
Normal file
88
src/users/models.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import uuid
|
||||
from django.contrib.auth.models import AbstractUser, UserManager
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from model_utils.models import TimeStampedModel, TimeFramedModel
|
||||
|
||||
from core.utils import generate_random_string
|
||||
|
||||
class User(AbstractUser):
|
||||
email = models.EmailField(_("email address"), blank=False, unique=True)
|
||||
uid = models.UUIDField(blank=False, null=False, verbose_name=_('UUID'))
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('User')
|
||||
verbose_name_plural = _('Users')
|
||||
|
||||
def __str__(self):
|
||||
if self.get_full_name():
|
||||
return self.get_full_name()
|
||||
else:
|
||||
return self.email
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.uid:
|
||||
self.uid = uuid.uuid4()
|
||||
super(User, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class ConfirmationCode(TimeStampedModel):
|
||||
email = models.EmailField(_("email address"), blank=True, null=True)
|
||||
code = models.CharField(max_length=50, blank=False, null=False, unique=True, verbose_name=_('Code'))
|
||||
registration_id = models.IntegerField(blank=True, null=True, verbose_name=_('Registration ID'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Confirmation code')
|
||||
verbose_name_plural = _('Confirmation codes')
|
||||
|
||||
def __str__(self):
|
||||
return self.code
|
||||
|
||||
|
||||
class SecurityCode(TimeStampedModel):
|
||||
email = models.EmailField(_("email address"), unique=True, blank=True, null=True)
|
||||
code = models.CharField(max_length=10, blank=False, null=False, unique=True, verbose_name=_('Code'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Security code')
|
||||
verbose_name_plural = _('Security codes')
|
||||
|
||||
def __str__(self):
|
||||
return self.email
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
def generate_code():
|
||||
# check if code is already in table
|
||||
code = generate_random_string(10)
|
||||
if SecurityCode.objects.filter(code=code).exists():
|
||||
generate_code()
|
||||
|
||||
return code
|
||||
|
||||
if not self.code:
|
||||
self.code = generate_code()
|
||||
|
||||
super(SecurityCode, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_or_create_user(email):
|
||||
password = None
|
||||
try:
|
||||
user, created = User.objects.get_or_create(email=email, username=email)
|
||||
# if user does not have a password, generate one
|
||||
if created:
|
||||
password = generate_random_string(8)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user, password
|
||||
except User.MultipleObjectsReturned:
|
||||
return User.objects.filter(email=email).first(), password
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
3
src/users/tests.py
Normal file
3
src/users/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
22
src/users/urls.py
Normal file
22
src/users/urls.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.urls import path
|
||||
from .views import LoginView, LogoutView, ChangePasswordView
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"login/",
|
||||
LoginView.as_view(template_name="users/login.html"),
|
||||
name="login",
|
||||
),
|
||||
path(
|
||||
"logout/",
|
||||
LogoutView.as_view(),
|
||||
name="logout",
|
||||
),
|
||||
|
||||
path(
|
||||
"change_password/",
|
||||
ChangePasswordView.as_view(template_name="users/change_password.html"),
|
||||
name="change_password",
|
||||
),
|
||||
|
||||
]
|
||||
108
src/users/views.py
Normal file
108
src/users/views.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from django.utils.http import urlencode
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth import login, logout
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import RedirectView, FormView, TemplateView
|
||||
from django.core.cache import cache
|
||||
|
||||
from users.forms import LoginForm, UserRegistrationForm, ChangePasswordForm, ForgotPasswordForm, ResetPasswordForm
|
||||
from users.models import User
|
||||
|
||||
|
||||
class LoginView(TemplateView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
data = self.get_context_data()
|
||||
data['form'] = LoginForm()
|
||||
request.session.set_test_cookie()
|
||||
return self.render_to_response(data)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
data = self.get_context_data()
|
||||
data['form'] = LoginForm(request, request.POST)
|
||||
|
||||
if data['form'].is_valid():
|
||||
try:
|
||||
user = data['form'].get_user()
|
||||
if user is None:
|
||||
raise Exception('User does not exist')
|
||||
login(request, user)
|
||||
messages.success(request, 'Login successful')
|
||||
|
||||
return HttpResponseRedirect(
|
||||
reverse_lazy('index')
|
||||
)
|
||||
except Exception as e:
|
||||
if 'invalid password' in str(e).lower():
|
||||
messages.error(request, _('Invalid password'))
|
||||
elif 'user is not active' in str(e).lower():
|
||||
messages.error(request, _('User is not active'))
|
||||
elif 'user does not exist' in str(e).lower():
|
||||
messages.error(request, _('User does not exist'))
|
||||
else:
|
||||
messages.error(request, e)
|
||||
|
||||
else:
|
||||
for field, errors in data['form'].errors.items():
|
||||
for error in errors:
|
||||
messages.error(request, f'{field}: {error}')
|
||||
|
||||
return self.render_to_response(data)
|
||||
|
||||
|
||||
class LogoutView(LoginRequiredMixin, RedirectView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
logout(request)
|
||||
messages.success(request, _('You have been logged out.'))
|
||||
|
||||
return HttpResponseRedirect(
|
||||
reverse_lazy('index')
|
||||
)
|
||||
|
||||
|
||||
class ChangePasswordView(LoginRequiredMixin, TemplateView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
data = self.get_context_data()
|
||||
data['form'] = ChangePasswordForm()
|
||||
return self.render_to_response(data)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
data = self.get_context_data()
|
||||
data['form'] = ChangePasswordForm(request.POST)
|
||||
|
||||
if data['form'].is_valid():
|
||||
# check old password
|
||||
if not data['form'].cleaned_data['password']:
|
||||
messages.error(request, _('Old password is required'))
|
||||
return self.render_to_response(data)
|
||||
|
||||
if not request.user.check_password(data['form'].cleaned_data['password']):
|
||||
messages.error(request, _('Invalid actual password'))
|
||||
return self.render_to_response(data)
|
||||
|
||||
if data['form'].cleaned_data['password1'] != data['form'].cleaned_data['password1']:
|
||||
messages.error(request, _('Passwords do not match'))
|
||||
return self.render_to_response(data)
|
||||
|
||||
# change password
|
||||
request.user.set_password(data['form'].cleaned_data['password1'])
|
||||
request.user.save()
|
||||
|
||||
# re-login
|
||||
login(request, request.user)
|
||||
messages.success(request, _('Password changed successfully'))
|
||||
|
||||
return HttpResponseRedirect(
|
||||
reverse_lazy('index')
|
||||
)
|
||||
else:
|
||||
for field, errors in data['form'].errors.items():
|
||||
for error in errors:
|
||||
messages.error(request, f'{field}: {error}')
|
||||
|
||||
return self.render_to_response(data)
|
||||
|
||||
Reference in New Issue
Block a user