🚀 first commit

This commit is contained in:
2025-03-06 12:51:11 +01:00
commit a47e9d157e
387 changed files with 93188 additions and 0 deletions

0
src/users/__init__.py Normal file
View File

41
src/users/admin.py Normal file
View 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
View 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
View 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
View 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

View 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()),
],
),
]

View 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),
]

View File

88
src/users/models.py Normal file
View 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
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

22
src/users/urls.py Normal file
View 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
View 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)