From 5343728471276810ce3f808f6b557271d5991384 Mon Sep 17 00:00:00 2001 From: Siobhan Tully Date: Fri, 26 Apr 2013 19:30:27 -0400 Subject: [PATCH] Added in PLUser as custom user, replacing the django base user. Reformatted initial_data for content with more readable 4 space indent for ability to do diffs --- plstackapi/core/admin.py | 102 ++++++++++--- plstackapi/core/fixtures/initial_data.json | 168 ++++++++++++++++++++- plstackapi/core/models/__init__.py | 2 +- plstackapi/core/models/key.py | 4 +- plstackapi/core/models/pluser.py | 101 +++++++++++++ plstackapi/core/models/site.py | 2 +- plstackapi/core/models/slice.py | 4 +- plstackapi/core/models/sliver.py | 2 +- plstackapi/core/models/user.py | 44 ------ plstackapi/planetstack/settings.py | 2 + 10 files changed, 361 insertions(+), 70 deletions(-) create mode 100644 plstackapi/core/models/pluser.py delete mode 100644 plstackapi/core/models/user.py diff --git a/plstackapi/core/admin.py b/plstackapi/core/admin.py index e53b245..7e19cf1 100644 --- a/plstackapi/core/admin.py +++ b/plstackapi/core/admin.py @@ -1,10 +1,13 @@ from plstackapi.core.models import Site from plstackapi.core.models import * from django.contrib import admin +from django.contrib.auth.models import Group + from django import forms from django.utils.safestring import mark_safe from django.contrib.auth.admin import UserAdmin from django.contrib.admin.widgets import FilteredSelectMultiple +from django.contrib.auth.forms import ReadOnlyPasswordHashField class ReadonlyTabularInline(admin.TabularInline): @@ -83,22 +86,6 @@ class SiteAdmin(admin.ModelAdmin): inlines = [NodeInline,] search_fields = ['name'] -class UserForm(forms.ModelForm): - class Meta: - password = forms.CharField(widget=forms.PasswordInput) - model = User - widgets = { - 'password': forms.PasswordInput(), - } - -class UserAdmin(admin.ModelAdmin): - form = UserForm - fieldsets = [ - ('User', {'fields': ['firstname', 'lastname', 'email', 'password', 'phone', 'user_url', 'is_admin', 'site']}) - ] - list_display = ['firstname', 'lastname', 'email', 'password', 'phone', 'user_url', 'is_admin', 'site'] - search_fields = ['email'] - class KeyAdmin(admin.ModelAdmin): fieldsets = [ ('Key', {'fields': ['name', 'key', 'type', 'blacklisted', 'user']}) @@ -112,6 +99,7 @@ class SliceAdmin(PlanetStackBaseAdmin): class SubnetAdmin(admin.ModelAdmin): fields = ['cidr', 'ip_version', 'start', 'end', 'slice'] + list_display = ('slice','cidr', 'start', 'end', 'ip_version' ) class ImageAdmin(admin.ModelAdmin): fields = ['image_id', 'name', 'disk_format', 'container_format'] @@ -136,7 +124,7 @@ class SliverForm(forms.ModelForm): model = Sliver widgets = { 'ip': PlainTextWidget(), - } + } class SliverAdmin(admin.ModelAdmin): form = SliverForm @@ -145,6 +133,85 @@ class SliverAdmin(admin.ModelAdmin): ] list_display = ['ip', 'name', 'slice', 'flavor', 'image', 'key', 'node', 'deploymentNetwork'] + +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 = PLUser + fields = ('email', 'firstname', 'lastname', 'phone', 'site') + + 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 = PLUser + + 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 PLUserAdmin(UserAdmin): + class Meta: + app_label = "core" + + # 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', 'site', 'firstname', 'lastname', 'last_login') + list_filter = ('site',) + fieldsets = ( + (None, {'fields': ('email', 'password')}), + ('Personal info', {'fields': ('firstname','lastname','phone','site')}), + #('Important dates', {'fields': ('last_login',)}), + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'firstname', 'lastname', 'phone', 'site', 'password1', 'password2')} + ), + ) + search_fields = ('email',) + ordering = ('email',) + filter_horizontal = () + +# Now register the new UserAdmin... +admin.site.register(PLUser, PLUserAdmin) +# ... and, since we're not using Django's builtin permissions, +# unregister the Group model from admin. +admin.site.unregister(Group) + admin.site.register(Site, SiteAdmin) admin.site.register(SitePrivilege) admin.site.register(Slice, SliceAdmin) @@ -156,6 +223,5 @@ admin.site.register(Sliver, SliverAdmin) admin.site.register(Flavor) admin.site.register(Key, KeyAdmin) admin.site.register(Role, RoleAdmin) -admin.site.register(User, UserAdmin) admin.site.register(DeploymentNetwork, DeploymentNetworkAdmin) diff --git a/plstackapi/core/fixtures/initial_data.json b/plstackapi/core/fixtures/initial_data.json index 47f5175..638b00d 100644 --- a/plstackapi/core/fixtures/initial_data.json +++ b/plstackapi/core/fixtures/initial_data.json @@ -1 +1,167 @@ -[{"pk": 1, "model": "core.deploymentnetwork", "fields": {"updated": "2013-04-03T22:57:09.331Z", "name": "VICCI", "created": "2013-04-03T22:57:09.331Z"}}, {"pk": 2, "model": "core.deploymentnetwork", "fields": {"updated": "2013-04-03T22:57:15.013Z", "name": "VINI", "created": "2013-04-03T22:57:15.013Z"}}, {"pk": 3, "model": "core.deploymentnetwork", "fields": {"updated": "2013-04-03T22:57:23.015Z", "name": "PlanetLab Classic", "created": "2013-04-03T22:57:23.015Z"}}, {"pk": 4, "model": "core.deploymentnetwork", "fields": {"updated": "2013-04-03T22:57:29.569Z", "name": "GENI", "created": "2013-04-03T22:57:29.569Z"}}, {"pk": 1, "model": "core.site", "fields": {"updated": "2013-04-05T15:21:04.135Z", "name": "Princeton University", "created": "2013-04-03T23:00:10.085Z", "tenant_id": "", "enabled": true, "longitude": -74.6524, "site_url": "http://princeton.edu/", "login_base": "princeton", "latitude": 40.3502, "is_public": true, "deployments": [3, 4], "abbreviated_name": ""}}, {"pk": 2, "model": "core.site", "fields": {"updated": "2013-04-05T15:42:36.517Z", "name": "Stanford University", "created": "2013-04-03T23:03:51.742Z", "tenant_id": "", "enabled": true, "longitude": -122.172, "site_url": "http://www.stanford.edu/", "login_base": "stanford", "latitude": 37.4294, "is_public": true, "deployments": [], "abbreviated_name": ""}}, {"pk": 3, "model": "core.site", "fields": {"updated": "2013-04-05T15:42:17.263Z", "name": "Georgia Institute of Technology", "created": "2013-04-03T23:05:51.984Z", "tenant_id": "", "enabled": true, "longitude": -84.3976, "site_url": "http://www.gatech.edu/", "login_base": "gt", "latitude": 33.7772, "is_public": true, "deployments": [], "abbreviated_name": ""}}, {"pk": 4, "model": "core.site", "fields": {"updated": "2013-04-05T15:39:27.501Z", "name": "University of Washington", "created": "2013-04-03T23:09:52.337Z", "tenant_id": "", "enabled": true, "longitude": -122.313, "site_url": "https://www.washington.edu/", "login_base": "uw", "latitude": 47.6531, "is_public": true, "deployments": [], "abbreviated_name": ""}}, {"pk": 5, "model": "core.site", "fields": {"updated": "2013-04-05T15:38:56.889Z", "name": "ETH Zuerich - Computer Science", "created": "2013-04-03T23:14:11.072Z", "tenant_id": "", "enabled": true, "longitude": 8.54513, "site_url": "http://www.inf.ethz.ch/", "login_base": "ethzcs", "latitude": 47.3794, "is_public": true, "deployments": [], "abbreviated_name": ""}}, {"pk": 6, "model": "core.site", "fields": {"updated": "2013-04-05T15:38:15.960Z", "name": "Max Planck Institute for Software Systems", "created": "2013-04-03T23:19:38.789Z", "tenant_id": "", "enabled": true, "longitude": 6.589, "site_url": "http://www.mpi-sws.mpg.de/", "login_base": "mpisws", "latitude": 49.14, "is_public": true, "deployments": [], "abbreviated_name": ""}}, {"pk": 7, "model": "core.site", "fields": {"updated": "2013-04-05T15:37:32.185Z", "name": "University of Tokyo", "created": "2013-04-03T23:20:49.815Z", "tenant_id": "", "enabled": true, "longitude": 139.5, "site_url": "http://www.planet-lab-jp.org/", "login_base": "utokyo", "latitude": 35.75, "is_public": true, "deployments": [], "abbreviated_name": ""}}] \ No newline at end of file +[ +{ + "pk": 1, + "model": "core.deploymentnetwork", + "fields": { + "updated": "2013-04-03T22:57:09.331Z", + "name": "VICCI", + "created": "2013-04-03T22:57:09.331Z" + } +}, +{ + "pk": 2, + "model": "core.deploymentnetwork", + "fields": { + "updated": "2013-04-03T22:57:15.013Z", + "name": "VINI", + "created": "2013-04-03T22:57:15.013Z" + } +}, +{ + "pk": 3, + "model": "core.deploymentnetwork", + "fields": { + "updated": "2013-04-03T22:57:23.015Z", + "name": "PlanetLab Classic", + "created": "2013-04-03T22:57:23.015Z" + } +}, +{ + "pk": 4, + "model": "core.deploymentnetwork", + "fields": { + "updated": "2013-04-03T22:57:29.569Z", + "name": "GENI", + "created": "2013-04-03T22:57:29.569Z" + } +}, +{ + "pk": 1, + "model": "core.site", + "fields": { + "updated": "2013-04-05T15:21:04.135Z", + "name": "Princeton University", + "created": "2013-04-03T23:00:10.085Z", + "tenant_id": "", + "enabled": true, + "longitude": -74.6524, + "site_url": "http://princeton.edu/", + "login_base": "princeton", + "latitude": 40.3502, + "is_public": true, + "deployments": [ + 3, + 4 + ], + "abbreviated_name": "" + } +}, +{ + "pk": 2, + "model": "core.site", + "fields": { + "updated": "2013-04-05T15:42:36.517Z", + "name": "Stanford University", + "created": "2013-04-03T23:03:51.742Z", + "tenant_id": "", + "enabled": true, + "longitude": -122.172, + "site_url": "http://www.stanford.edu/", + "login_base": "stanford", + "latitude": 37.4294, + "is_public": true, + "deployments": [], + "abbreviated_name": "" + } +}, +{ + "pk": 3, + "model": "core.site", + "fields": { + "updated": "2013-04-05T15:42:17.263Z", + "name": "Georgia Institute of Technology", + "created": "2013-04-03T23:05:51.984Z", + "tenant_id": "", + "enabled": true, + "longitude": -84.3976, + "site_url": "http://www.gatech.edu/", + "login_base": "gt", + "latitude": 33.7772, + "is_public": true, + "deployments": [], + "abbreviated_name": "" + } +}, +{ + "pk": 4, + "model": "core.site", + "fields": { + "updated": "2013-04-05T15:39:27.501Z", + "name": "University of Washington", + "created": "2013-04-03T23:09:52.337Z", + "tenant_id": "", + "enabled": true, + "longitude": -122.313, + "site_url": "https://www.washington.edu/", + "login_base": "uw", + "latitude": 47.6531, + "is_public": true, + "deployments": [], + "abbreviated_name": "" + } +}, +{ + "pk": 5, + "model": "core.site", + "fields": { + "updated": "2013-04-05T15:38:56.889Z", + "name": "ETH Zuerich - Computer Science", + "created": "2013-04-03T23:14:11.072Z", + "tenant_id": "", + "enabled": true, + "longitude": 8.54513, + "site_url": "http://www.inf.ethz.ch/", + "login_base": "ethzcs", + "latitude": 47.3794, + "is_public": true, + "deployments": [], + "abbreviated_name": "" + } +}, +{ + "pk": 6, + "model": "core.site", + "fields": { + "updated": "2013-04-05T15:38:15.960Z", + "name": "Max Planck Institute for Software Systems", + "created": "2013-04-03T23:19:38.789Z", + "tenant_id": "", + "enabled": true, + "longitude": 6.589, + "site_url": "http://www.mpi-sws.mpg.de/", + "login_base": "mpisws", + "latitude": 49.14, + "is_public": true, + "deployments": [], + "abbreviated_name": "" + } +}, +{ + "pk": 7, + "model": "core.site", + "fields": { + "updated": "2013-04-05T15:37:32.185Z", + "name": "University of Tokyo", + "created": "2013-04-03T23:20:49.815Z", + "tenant_id": "", + "enabled": true, + "longitude": 139.5, + "site_url": "http://www.planet-lab-jp.org/", + "login_base": "utokyo", + "latitude": 35.75, + "is_public": true, + "deployments": [], + "abbreviated_name": "" + } +} +] diff --git a/plstackapi/core/models/__init__.py b/plstackapi/core/models/__init__.py index 1ecda7e..5f962e3 100644 --- a/plstackapi/core/models/__init__.py +++ b/plstackapi/core/models/__init__.py @@ -4,7 +4,7 @@ from .site import Site from .site import SitePrivilege from .flavor import Flavor from .image import Image -from .user import User +from .user import PLUser from .role import Role from .key import Key from .node import Node diff --git a/plstackapi/core/models/key.py b/plstackapi/core/models/key.py index e224831..9d4b139 100644 --- a/plstackapi/core/models/key.py +++ b/plstackapi/core/models/key.py @@ -1,7 +1,7 @@ import os from django.db import models from plstackapi.core.models import PlCoreBase -from plstackapi.core.models import User +from plstackapi.core.models import PLUser from plstackapi.openstack.driver import OpenStackDriver # Create your models here. @@ -12,7 +12,7 @@ class Key(PlCoreBase): key = models.CharField(max_length=512) type = models.CharField(max_length=256) blacklisted = models.BooleanField(default=False) - user = models.ForeignKey(User, related_name='keys') + user = models.ForeignKey(PLUser, related_name='keys') def __unicode__(self): return u'%s' % (self.name) diff --git a/plstackapi/core/models/pluser.py b/plstackapi/core/models/pluser.py new file mode 100644 index 0000000..a59cb97 --- /dev/null +++ b/plstackapi/core/models/pluser.py @@ -0,0 +1,101 @@ +import os +import datetime +from django.db import models +from core.models import PlCoreBase +from core.models import Site +from django.contrib.auth.models import User, AbstractBaseUser, UserManager, BaseUserManager + +# Create your models here. + +class PLUserManager(BaseUserManager): + def create_user(self, email, firstname, lastname, password=None): + """ + Creates and saves a User with the given email, date of + birth and password. + """ + if not email: + raise ValueError('Users must have an email address') + + user = self.model( + email=PLUserManager.normalize_email(email), + firstname=firstname, + lastname=lastname + ) + + user.set_password(password) + user.is_admin = True + user.save(using=self._db) + return user + + def create_superuser(self, email, firstname, lastname, password): + """ + Creates and saves a superuser with the given email, date of + birth and password. + """ + user = self.create_user(email, + password=password, + firstname=firstname, + lastname=lastname + ) + user.is_admin = True + user.save(using=self._db) + return user + + +class PLUser(AbstractBaseUser): + + class Meta: + app_label = "core" + + email = models.EmailField( + verbose_name='email address', + max_length=255, + unique=True, + db_index=True, + ) + + + firstname = models.CharField(help_text="person's given name", max_length=200) + lastname = models.CharField(help_text="person's surname", max_length=200) + + phone = models.CharField(null=True, blank=True, help_text="phone number contact", max_length=100) + user_url = models.URLField(null=True, blank=True) + site = models.ForeignKey(Site, related_name='users', verbose_name="Site this user will be homed too", null=True) + + is_active = models.BooleanField(default=True) + is_admin = models.BooleanField(default=True) + is_staff = models.BooleanField(default=True) + + objects = PLUserManager() + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['firstname', 'lastname'] + + def get_full_name(self): + # The user is identified by their email address + return self.email + + def get_short_name(self): + # The user is identified by their email address + return self.email + + def __unicode__(self): + return self.email + + def has_perm(self, perm, obj=None): + "Does the user have a specific permission?" + # Simplest possible answer: Yes, always + return True + + def has_module_perms(self, app_label): + "Does the user have permissions to view the app `app_label`?" + # Simplest possible answer: Yes, always + return True + + @property + def is_staff(self): + "Is the user a member of staff?" + # Simplest possible answer: All admins are staff + return self.is_admin + + diff --git a/plstackapi/core/models/site.py b/plstackapi/core/models/site.py index 67257af..1e1cbe4 100644 --- a/plstackapi/core/models/site.py +++ b/plstackapi/core/models/site.py @@ -49,7 +49,7 @@ class Site(PlCoreBase): class SitePrivilege(PlCoreBase): - user = models.ForeignKey('User', related_name='site_privileges') + user = models.ForeignKey('PLUser', related_name='site_privileges') site = models.ForeignKey('Site', related_name='site_privileges') role = models.ForeignKey('Role') diff --git a/plstackapi/core/models/slice.py b/plstackapi/core/models/slice.py index fe16402..27184dc 100644 --- a/plstackapi/core/models/slice.py +++ b/plstackapi/core/models/slice.py @@ -2,7 +2,7 @@ import os from django.db import models from plstackapi.core.models import PlCoreBase from plstackapi.core.models import Site -from plstackapi.core.models import User +from plstackapi.core.models import PLUser from plstackapi.core.models import Role from plstackapi.core.models import DeploymentNetwork from plstackapi.openstack.driver import OpenStackDriver @@ -54,7 +54,7 @@ class Slice(PlCoreBase): super(Slice, self).delete(*args, **kwds) class SliceMembership(PlCoreBase): - user = models.ForeignKey('User', related_name='slice_memberships') + user = models.ForeignKey('PLUser', related_name='slice_memberships') slice = models.ForeignKey('Slice', related_name='slice_memberships') role = models.ForeignKey('Role') diff --git a/plstackapi/core/models/sliver.py b/plstackapi/core/models/sliver.py index 2eabf23..27b9ab1 100644 --- a/plstackapi/core/models/sliver.py +++ b/plstackapi/core/models/sliver.py @@ -21,7 +21,7 @@ class Sliver(PlCoreBase): key = models.ForeignKey(Key, related_name='slivers') slice = models.ForeignKey(Slice, related_name='slivers') node = models.ForeignKey(Node, related_name='slivers') - deploymentNetwork = models.ForeignKey(DeploymentNetwork, related_name='sliver_deploymentNetwork') + deploymentNetwork = models.ForeignKey(DeploymentNetwork, verbose_name='deployment', related_name='sliver_deploymentNetwork') def __unicode__(self): return u'%s::%s' % (self.slice, self.deploymentNetwork) diff --git a/plstackapi/core/models/user.py b/plstackapi/core/models/user.py deleted file mode 100644 index 30bd2a9..0000000 --- a/plstackapi/core/models/user.py +++ /dev/null @@ -1,44 +0,0 @@ -import os -import datetime -from django.db import models -from plstackapi.core.models import PlCoreBase -from plstackapi.core.models import Site -from plstackapi.openstack.driver import OpenStackDriver - - -# Create your models here. - -class User(PlCoreBase): - user_id = models.CharField(max_length=256, unique=True) - firstname = models.CharField(help_text="person's given name", max_length=200) - lastname = models.CharField(help_text="person's surname", max_length=200) - email = models.EmailField(help_text="e-mail address", null=True) - password = models.CharField(max_length=256, null=True, blank=True) - - phone = models.CharField(null=True, blank=True, help_text="phone number contact", max_length=100) - user_url = models.URLField(null=True, blank=True) - is_admin = models.BooleanField(default=False) - enabled = models.BooleanField(default=True, help_text="Status for this User") - site = models.ForeignKey(Site, related_name='users', verbose_name="Site this user will be homed too") - - def __unicode__(self): return u'%s' % (self.email) - - def save(self, *args, **kwds): - driver = OpenStackDriver() - if not self.user_id: - name = self.email[:self.email.find('@')] - user_fields = {'name': name, - 'email': self.email, - 'password': self.password, - 'enabled': self.enabled} - user = driver.create_user(**user_fields) - self.user_id = user.id - - self.password = None - super(User, self).save(*args, **kwds) - - def delete(self, *args, **kwds): - driver = OpenStackDriver() - if self.user_id: - driver.delete_user(self.user_id) - super(User, self).delete(*args, **kwds) diff --git a/plstackapi/planetstack/settings.py b/plstackapi/planetstack/settings.py index f638c26..40e839a 100644 --- a/plstackapi/planetstack/settings.py +++ b/plstackapi/planetstack/settings.py @@ -9,6 +9,8 @@ ADMINS = ( MANAGERS = ADMINS +AUTH_USER_MODEL = 'core.PLUser' + from plstackapi.planetstack.config import Config config = Config() DATABASES = { -- 2.43.0