diff --git a/requirements.txt b/requirements.txt index e6ac912..cea7e23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ argon2-cffi>=19.1.0 cffi>=1.12.3 dj-database-url>=0.5.0 Django>=2.2.2 -django-authtools>=1.6.0 djangorestframework>=3.9.4 psycopg2-binary>=2.8.2 pycparser>=2.19 diff --git a/savepointradio/core/admin.py b/savepointradio/core/admin.py index 0d572eb..d81d3eb 100644 --- a/savepointradio/core/admin.py +++ b/savepointradio/core/admin.py @@ -1,10 +1,41 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from django.utils.translation import ugettext_lazy as _ -from authtools.admin import UserAdmin - +# from authtools.admin import UserAdmin +from .forms import RadioUserChangeForm, RadioUserCreationForm from .models import RadioUser @admin.register(RadioUser) class RadioUserAdmin(UserAdmin): - pass + add_form = RadioUserCreationForm + form = RadioUserChangeForm + model = RadioUser + + list_display = ('is_active', 'email', 'name', 'is_superuser', 'is_staff',) + list_display_links = ('email', 'name',) + list_filter = ('is_superuser', 'is_staff', 'is_active',) + fieldsets = ( + (None, { + 'fields': ('email', 'name', 'password', 'is_dj',), + }), + (_('Permissions'), { + 'fields': ('is_active', 'is_staff', 'is_superuser', + 'groups', 'user_permissions',), + }), + (_('Important dates'), { + 'fields': ('last_login', 'date_joined',), + }), + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'name', 'password1', 'password2', + 'is_staff', 'is_active', 'is_superuser',), + }), + ) + search_fields = ('email', 'name',) + ordering = None + filter_horizontal = ('groups', 'user_permissions',) + readonly_fields = ('last_login', 'date_joined') diff --git a/savepointradio/core/forms.py b/savepointradio/core/forms.py new file mode 100644 index 0000000..a3ad890 --- /dev/null +++ b/savepointradio/core/forms.py @@ -0,0 +1,28 @@ +from django import forms +from django.contrib.auth.forms import UserChangeForm, UserCreationForm +from django.utils.translation import ugettext_lazy as _ + +from .models import RadioUser + + +class RadioUserChangeForm(UserChangeForm): + ''' + A form for updating users for the radio. + ''' + + class Meta: + model = RadioUser + fields = '__all__' + + +class RadioUserCreationForm(UserCreationForm): + ''' + A form for creating new users for the radio. + ''' + + class Meta: + model = RadioUser + fields = ('email', 'name') + + + diff --git a/savepointradio/core/managers.py b/savepointradio/core/managers.py new file mode 100644 index 0000000..2db887e --- /dev/null +++ b/savepointradio/core/managers.py @@ -0,0 +1,43 @@ +from django.contrib.auth.base_user import BaseUserManager +from django.utils.translation import ugettext_lazy as _ + + +class RadioUserManager(BaseUserManager): + ''' + Custom user model manager where email is the unique identifier for + authentication instead of the username. + + Credit for assistance goes to Michael Herman: + https://testdriven.io/blog/django-custom-user-model/ + ''' + + def create_user(self, email, password=None, **extra_fields): + ''' + Create and save a user. + ''' + if not email: + raise ValueError(_('You must provide an email.')) + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None, **extra_fields): + ''' + Create and save a superuser. + ''' + extra_fields.setdefault('is_staff', True) + extra_fields.setdefault('is_superuser', True) + extra_fields.setdefault('is_active', True) + extra_fields.setdefault('is_dj', False) + + if extra_fields.get('is_staff') is not True: + raise ValueError( + _('A superuser must have "is_staff" set to True.') + ) + if extra_fields.get('is_superuser') is not True: + raise ValueError( + _('A superuser must have "is_supervisor" set to True.') + ) + return self.create_user(email, password, **extra_fields) diff --git a/savepointradio/core/migrations/0001_initial.py b/savepointradio/core/migrations/0001_initial.py index 7033013..cf5d06f 100644 --- a/savepointradio/core/migrations/0001_initial.py +++ b/savepointradio/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0 on 2017-12-29 13:36 +# Generated by Django 3.0.2 on 2020-01-21 16:49 from django.db import migrations, models import django.utils.timezone @@ -9,31 +9,10 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0009_alter_user_last_name_max_length'), + ('auth', '0011_update_proxy_permissions'), ] operations = [ - migrations.CreateModel( - name='RadioUser', - fields=[ - ('id', models.AutoField(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')), - ('email', models.EmailField(max_length=255, unique=True, verbose_name='email address')), - ('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')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ('is_dj', models.BooleanField(default=False, help_text='Designates whether this user is the automated dj account or is a real person account.', verbose_name='dj status')), - ('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={ - 'ordering': ['name', 'email'], - 'abstract': False, - }, - ), migrations.CreateModel( name='Setting', fields=[ @@ -44,4 +23,24 @@ class Migration(migrations.Migration): ('data', models.TextField(verbose_name='data')), ], ), + migrations.CreateModel( + name='RadioUser', + fields=[ + ('id', models.AutoField(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')), + ('email', models.EmailField(max_length=255, unique=True, verbose_name='email address')), + ('name', models.CharField(max_length=255, verbose_name='name')), + ('is_dj', models.BooleanField(default=False, help_text='Designates whether the user is the automated dj account or is a real person account.', verbose_name='dj status')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into the admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether the 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')), + ('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={ + 'ordering': ['name', 'email'], + }, + ), ] diff --git a/savepointradio/core/models.py b/savepointradio/core/models.py index cb389e1..bed3cf1 100644 --- a/savepointradio/core/models.py +++ b/savepointradio/core/models.py @@ -1,19 +1,61 @@ from django.db import models -from django.utils.translation import gettext_lazy as _ +from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin +from django.core.mail import send_mail +from django.utils.translation import ugettext_lazy as _ +from django.utils import timezone -from authtools.models import AbstractNamedUser +from .managers import RadioUserManager -class RadioUser(AbstractNamedUser): +class RadioUser(AbstractBaseUser, PermissionsMixin): """ Custom user model which uses email as the main identifier and adds a flag for whether the user is the radio DJ. + + Reworked from the django-authtools module. """ - is_dj = models.BooleanField(_('dj status'), - default=False, - help_text=_('Designates whether this user is ' - 'the automated dj account or is a ' - 'real person account.')) + email = models.EmailField(_('email address'), max_length=255, unique=True) + name = models.CharField(_('name'), max_length=255) + + is_dj = models.BooleanField(_('dj status'), default=False, + help_text=_('Designates whether the user is the automated dj account ' + 'or is a real person account.') + ) + is_staff = models.BooleanField(_('staff status'), default=False, + help_text=_('Designates whether the user can log into the admin site.') + ) + is_active = models.BooleanField(_('active'), default=True, + help_text=_('Designates whether the user should be treated as active. ' + 'Unselect this instead of deleting accounts.') + ) + date_joined = models.DateTimeField(_('date joined'), default=timezone.now) + + objects = RadioUserManager() + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['name'] + + class Meta: + ordering = ['name', 'email'] + + def get_full_name(self): + return self.name + + def get_short_name(self): + return self.name + + def email_user(self, subject, message, from_email=None, **kwargs): + ''' + Sends an email to this user. + ''' + + send_mail(subject, message, from_email, [self.email], **kwargs) + + def __str__(self): + return '{name} <{email}>'.format( + name=self.name, + email=self.email, + ) class Setting(models.Model): diff --git a/savepointradio/savepointradio/settings.py b/savepointradio/savepointradio/settings.py index 1f92390..c7a892e 100644 --- a/savepointradio/savepointradio/settings.py +++ b/savepointradio/savepointradio/settings.py @@ -55,7 +55,6 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', - 'authtools', 'rest_framework', 'rest_framework.authtoken', diff --git a/savepointradio/savepointradio/urls.py b/savepointradio/savepointradio/urls.py index 5b8c5cb..1e1118c 100644 --- a/savepointradio/savepointradio/urls.py +++ b/savepointradio/savepointradio/urls.py @@ -2,7 +2,6 @@ from django.contrib import admin from django.urls import include, path urlpatterns = [ - path('accounts/', include('authtools.urls')), path('admin/', admin.site.urls), path('api/', include('api.urls')), ]