From e16fa08b915562c6ab77ce7bb79a9d766b5a4036 Mon Sep 17 00:00:00 2001 From: Mitch Riedstra Date: Thu, 2 Nov 2017 16:03:14 -0400 Subject: Initial setup to use a custom User model, I still need to figure out how to use the built in Django permissions though --- app/app/settings.py | 4 + app/dispatch/migrations/0001_initial.py | 14 +--- app/dispatch/migrations/0002_auto_20171024_2255.py | 27 ------- app/dispatch/migrations/0002_auto_20171102_1949.py | 65 ++++++++++++++++ app/dispatch/models.py | 13 ++-- app/dispatch/monkey_patch.py | 5 +- app/dispatch/views.py | 4 +- app/dispatchAuth/__init__.py | 0 app/dispatchAuth/admin.py | 90 ++++++++++++++++++++++ app/dispatchAuth/apps.py | 5 ++ app/dispatchAuth/migrations/0001_initial.py | 35 +++++++++ app/dispatchAuth/migrations/__init__.py | 0 app/dispatchAuth/models.py | 69 +++++++++++++++++ app/dispatchAuth/tests.py | 3 + app/dispatchAuth/views.py | 3 + 15 files changed, 289 insertions(+), 48 deletions(-) delete mode 100644 app/dispatch/migrations/0002_auto_20171024_2255.py create mode 100644 app/dispatch/migrations/0002_auto_20171102_1949.py create mode 100644 app/dispatchAuth/__init__.py create mode 100644 app/dispatchAuth/admin.py create mode 100644 app/dispatchAuth/apps.py create mode 100644 app/dispatchAuth/migrations/0001_initial.py create mode 100644 app/dispatchAuth/migrations/__init__.py create mode 100644 app/dispatchAuth/models.py create mode 100644 app/dispatchAuth/tests.py create mode 100644 app/dispatchAuth/views.py diff --git a/app/app/settings.py b/app/app/settings.py index f143931..f24a6b3 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -48,6 +48,9 @@ ALLOWED_HOSTS = CONFIG['allowed_hosts'] ADMINS = CONFIG['admins'] +AUTH_USER_MODEL = 'dispatchAuth.User' + + # Application definition INSTALLED_APPS = [ @@ -58,6 +61,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'dispatch.apps.DispatchConfig', + 'dispatchAuth.apps.DispatchauthConfig', 'auditlog', ] diff --git a/app/dispatch/migrations/0001_initial.py b/app/dispatch/migrations/0001_initial.py index 05c6c7d..a358b07 100644 --- a/app/dispatch/migrations/0001_initial.py +++ b/app/dispatch/migrations/0001_initial.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.5 on 2017-10-24 22:53 +# Generated by Django 1.11.5 on 2017-11-02 19:49 from __future__ import unicode_literals import dispatch.misc -from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -13,7 +11,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ @@ -37,7 +34,6 @@ class Migration(migrations.Migration): ('city', models.CharField(max_length=256)), ('state', models.CharField(max_length=256)), ('zip_code', models.CharField(max_length=256)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique=True)), ], ), migrations.CreateModel( @@ -48,9 +44,6 @@ class Migration(migrations.Migration): ('invoice_date', models.DateField()), ('due_date', models.DateField()), ('paid', models.BooleanField(default=False)), - ('bill_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bill_to', to='dispatch.Identity')), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to='dispatch.Identity')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( @@ -61,7 +54,6 @@ class Migration(migrations.Migration): ('description', models.CharField(max_length=256)), ('quantity', models.IntegerField()), ('amount', models.DecimalField(decimal_places=2, max_digits=19)), - ('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Invoice')), ], ), migrations.CreateModel( @@ -72,8 +64,6 @@ class Migration(migrations.Migration): ('description', models.CharField(max_length=256)), ('delivered_to', models.CharField(default='', max_length=256)), ('amount', models.DecimalField(decimal_places=2, default='0', max_digits=10)), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Customer')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( @@ -82,7 +72,6 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('description', models.CharField(max_length=256)), ('document', models.FileField(upload_to=dispatch.misc.paperwork_user_directory_path)), - ('load', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Load')), ], ), migrations.CreateModel( @@ -98,7 +87,6 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('number', models.IntegerField(default=1200)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique=True)), ], ), ] diff --git a/app/dispatch/migrations/0002_auto_20171024_2255.py b/app/dispatch/migrations/0002_auto_20171024_2255.py deleted file mode 100644 index bbf96d4..0000000 --- a/app/dispatch/migrations/0002_auto_20171024_2255.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.5 on 2017-10-24 22:55 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dispatch', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='identity', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='userinvoicenumber', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/dispatch/migrations/0002_auto_20171102_1949.py b/app/dispatch/migrations/0002_auto_20171102_1949.py new file mode 100644 index 0000000..4be45e9 --- /dev/null +++ b/app/dispatch/migrations/0002_auto_20171102_1949.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-11-02 19:49 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('dispatch', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='userinvoicenumber', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='paperwork', + name='load', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Load'), + ), + migrations.AddField( + model_name='load', + name='customer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Customer'), + ), + migrations.AddField( + model_name='load', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='invoiceitem', + name='invoice', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Invoice'), + ), + migrations.AddField( + model_name='invoice', + name='bill_to', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bill_to', to='dispatch.Identity'), + ), + migrations.AddField( + model_name='invoice', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to='dispatch.Identity'), + ), + migrations.AddField( + model_name='invoice', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='identity', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/app/dispatch/models.py b/app/dispatch/models.py index 21c06e0..fe0cdb3 100644 --- a/app/dispatch/models.py +++ b/app/dispatch/models.py @@ -2,15 +2,16 @@ from django.db import models from django.conf import settings from auditlog.registry import auditlog from auditlog.models import AuditlogHistoryField -from django.contrib.auth.models import User +# from django.contrib.auth import get_user_model from datetime import datetime from django.core.exceptions import ObjectDoesNotExist from .misc import get_week_dates, paperwork_user_directory_path - -from . import monkey_patch +from django.core.urlresolvers import reverse # Create your models here. + + class Customer(models.Model): history = AuditlogHistoryField() name = models.CharField(max_length=256) @@ -50,7 +51,7 @@ class Paperwork(models.Model): class Identity(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) + user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) name = models.CharField(max_length=256) address = models.CharField(max_length=256) city = models.CharField(max_length=256) @@ -81,14 +82,14 @@ class Settings(models.Model): class UserInvoiceNumber(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) + user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) number = models.IntegerField(default=1200) def get_absolute_url(self): return '/drivers/view/{:d}'.format(self.user.pk) class Invoice(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) owner = models.ForeignKey(Identity, on_delete=models.CASCADE, related_name="owner") bill_to = models.ForeignKey(Identity, on_delete=models.CASCADE, related_name="bill_to") invoice_id = models.IntegerField() diff --git a/app/dispatch/monkey_patch.py b/app/dispatch/monkey_patch.py index 3b87287..cc61b6e 100644 --- a/app/dispatch/monkey_patch.py +++ b/app/dispatch/monkey_patch.py @@ -1,6 +1,9 @@ -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse +# This should be going away soon with the addition +# of my custom user model + def get_absolute_url(self): return reverse('driver_details', kwargs={'pk': self.pk}) diff --git a/app/dispatch/views.py b/app/dispatch/views.py index 918cf0d..5a94775 100644 --- a/app/dispatch/views.py +++ b/app/dispatch/views.py @@ -15,7 +15,7 @@ from django.core.urlresolvers import reverse_lazy from dispatch.models import Customer, Load, Paperwork, \ Invoice, UserInvoiceNumber, Identity from dispatch.forms import AddPaperworkForm -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.contrib.auth.mixins import UserPassesTestMixin # from django.http import HttpResponseRedirect from .misc import get_week_dates, split_loads_by_day @@ -23,6 +23,8 @@ import datetime import re import os +User = get_user_model() + def home(request): return redirect(reverse('load_list')) diff --git a/app/dispatchAuth/__init__.py b/app/dispatchAuth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/dispatchAuth/admin.py b/app/dispatchAuth/admin.py new file mode 100644 index 0000000..67b1170 --- /dev/null +++ b/app/dispatchAuth/admin.py @@ -0,0 +1,90 @@ +from django import forms +from django.contrib import admin +from django.contrib.auth.models import Group +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.forms import ReadOnlyPasswordHashField + +# Register your models here. + + +from .models import User + +class UserCreationForm(forms.ModelForm): + """A form for creating new users. Includes all the required + fields, plus a repeated password.""" + password1 = forms.CharField(label='Password', widget=forms.PasswordInput) + password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) + + class Meta: + model = User + fields = ('email', 'first_name', 'last_name') + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError("Passwords don't match") + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super(UserCreationForm, self).save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user + + +class UserChangeForm(forms.ModelForm): + """A form for updating users. Includes all the fields on + the user, but replaces the password field with admin's + password hash display field. + """ + password = ReadOnlyPasswordHashField() + + class Meta: + model = User + fields = ('email', 'password', 'first_name', 'last_name', 'is_active', + 'is_superuser') + + def clean_password(self): + # Regardless of what the user provides, return the initial value. + # This is done here, rather than on the field, because the + # field does not have access to the initial value + return self.initial["password"] + + +class UserAdmin(BaseUserAdmin): + # The forms to add and change user instances + form = UserChangeForm + add_form = UserCreationForm + + # The fields to be used in displaying the User model. + # These override the definitions on the base UserAdmin + # that reference specific fields on auth.User. + list_display = ('email', 'is_superuser') + list_filter = ('is_superuser',) + fieldsets = ( + (None, {'fields': ('email', 'password')}), + ('Personal info', {'fields': ('first_name','last_name')}), + ('Permissions', {'fields': ('is_superuser',)}), + ) + # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin + # overrides get_fieldsets to use this attribute when creating a user. + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'first_name', 'last_name', 'password1', 'password2')} + ), + ) + search_fields = ('email',) + ordering = ('email',) + filter_horizontal = () + +# Now register the new UserAdmin... +admin.site.register(User, UserAdmin) + +# ... and, since we're not using Django's built-in permissions, +# unregister the Group model from admin. +# admin.site.unregister(Group) diff --git a/app/dispatchAuth/apps.py b/app/dispatchAuth/apps.py new file mode 100644 index 0000000..ba984ef --- /dev/null +++ b/app/dispatchAuth/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class DispatchauthConfig(AppConfig): + name = 'dispatchAuth' diff --git a/app/dispatchAuth/migrations/0001_initial.py b/app/dispatchAuth/migrations/0001_initial.py new file mode 100644 index 0000000..4a0f37c --- /dev/null +++ b/app/dispatchAuth/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-11-02 19:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0008_alter_user_username_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + 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')), + ('email', models.EmailField(max_length=256, unique=True)), + ('first_name', models.CharField(max_length=256)), + ('last_name', models.CharField(max_length=256)), + ('is_active', models.BooleanField(default=True)), + ('is_superuser', models.BooleanField(default=False)), + ('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={ + 'abstract': False, + }, + ), + ] diff --git a/app/dispatchAuth/migrations/__init__.py b/app/dispatchAuth/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/dispatchAuth/models.py b/app/dispatchAuth/models.py new file mode 100644 index 0000000..6c90dd0 --- /dev/null +++ b/app/dispatchAuth/models.py @@ -0,0 +1,69 @@ +from django.contrib.auth.models import AbstractUser, AbstractBaseUser, PermissionsMixin, BaseUserManager +from django.db import models + +# Create your models here. + + +class UserManager(BaseUserManager): + def create_user(self, email, first_name, last_name, password=None): + """ + Creates and saves a User with the given email, first name, + last name and password. + """ + if not email: + raise ValueError('Users must have an email address') + + user = self.model( + email=self.normalize_email(email), + first_name=first_name, + last_name=last_name, + ) + + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, first_name, last_name, password): + """ + Creates and saves a superuser with the given email, first name, + last name and password. + """ + user = self.create_user( + email=email, + first_name=first_name, + last_name=last_name, + password=password, + ) + user.is_superuser = True + user.save(using=self._db) + return user + + +class User(PermissionsMixin, AbstractBaseUser): + email = models.EmailField(max_length=256, unique=True) + first_name = models.CharField(max_length=256) + last_name = models.CharField(max_length=256) + is_active = models.BooleanField(default=True) + is_superuser = models.BooleanField(default=False) + + USERNAME_FIELD = 'email' + EMAIL_FIELD = 'email' + REQUIRED_FIELDS = ['first_name', 'last_name'] + + objects = UserManager() + + def get_full_name(self): + return "{} {}".format(self.first_name, self.last_name) + + def get_short_name(self): + return self.first_name + + def get_absolute_url(self): + return reverse('driver_details', kwargs={'pk': self.pk}) + + def __str__(self): + return self.email + + @property + def is_staff(self): + return self.is_superuser diff --git a/app/dispatchAuth/tests.py b/app/dispatchAuth/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/dispatchAuth/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/dispatchAuth/views.py b/app/dispatchAuth/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/app/dispatchAuth/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. -- cgit v1.2.3