Skip to content

Commit

Permalink
Merge pull request #1000 from DDMAL/jacky/email-login
Browse files Browse the repository at this point in the history
  • Loading branch information
jackyyzhang03 authored Jun 30, 2023
2 parents ce036f9 + 745098e commit 47aa7d2
Show file tree
Hide file tree
Showing 39 changed files with 277 additions and 97 deletions.
16 changes: 13 additions & 3 deletions rodan-client/code/src/js/Controllers/ControllerAuthentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export default class ControllerAuthentication extends BaseController
break;
case 401:
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, {response: request,
message: 'Incorrect username/password.'});
message: 'Incorrect email/password.'});
Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__AUTHENTICATION_LOGINREQUIRED);
break;
case 403:
Expand Down Expand Up @@ -209,7 +209,7 @@ export default class ControllerAuthentication extends BaseController
}
request.setRequestHeader('Accept', 'application/json');
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
request.send('username=' + options.username + '&password=' + options.password);
request.send('email=' + options.email + '&password=' + options.password);
}

/**
Expand Down Expand Up @@ -322,7 +322,7 @@ export default class ControllerAuthentication extends BaseController
{
var route = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_GET_ROUTE, 'auth-me');
var ajaxSettings = {success: (response) => this._handleSaveUserSuccess(response),
error: (response) => Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SYSTEM_HANDLE_ERROR, {response: response}),
error: (response) => this._handleSaveUserError(response),
type: 'PATCH',
url: route,
dataType: 'json',
Expand Down Expand Up @@ -356,6 +356,16 @@ export default class ControllerAuthentication extends BaseController
Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__USER_SAVED, {user: this._user});
}

/**
* Handles errors from saving user.
*/
_handleSaveUserError(response)
{
const errors = response.responseJSON;
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_FORM_VALIDATION_ERROR, { errors });
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_ERROR, {content: 'An error occured while saving the user.'});
}

/**
* Handle success response from changing password.
*/
Expand Down
1 change: 1 addition & 0 deletions rodan-client/code/src/js/Controllers/ControllerModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export default class ControllerModal extends BaseController
{
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW, options);
}
$('.modal-footer').removeClass('modal-footer-error');
}

/**
Expand Down
2 changes: 2 additions & 0 deletions rodan-client/code/src/js/Shared/RODAN_EVENTS.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ class RODAN_EVENTS
///////////////////////////////////////////////////////////////////////////////////////
/** Request an error be displayed. This is a "convenience" request -- if a modal is currently visible (which is probably related somehow to the error) the footer will be updated with the error message. If no modal visible, REQUEST__MODAL_SHOW will be called. Takes {content: Marionette.View OR string}. */
this.REQUEST__MODAL_ERROR = 'REQUEST__MODAL_ERROR';
/** Request form validation errors to be displayed. Takes {errors: {[string]: string[]}} mapping input field name to an array of error strings. */
this.REQUEST__MODAL_FORM_VALIDATION_ERROR = 'REQUEST__MODAL_FORM_VALIDATION_ERROR';
/** Request modal window to hide/close. */
this.REQUEST__MODAL_HIDE = 'REQUEST__MODAL_HIDE';
/** Request modal window to show/open with provided Marionette View. If another modal is currently open the request will not show. Takes {content: string, title: string}. */
Expand Down
4 changes: 2 additions & 2 deletions rodan-client/code/src/js/Views/Master/Main/Login/ViewLogin.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export default class ViewLogin extends Marionette.View
*/
_handleButton()
{
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__AUTHENTICATION_LOGIN, {username: this.ui.textUsername.val(), password: this.ui.textPassword.val()});
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__AUTHENTICATION_LOGIN, { email: this.ui.textEmail.val(), password: this.ui.textPassword.val() });
}
}
ViewLogin.prototype.modelEvents = {
'all': 'render'
};
ViewLogin.prototype.ui = {
textUsername: '#text-login_username',
textEmail: '#text-login_email',
textPassword: '#text-login_password',
buttonLogin: '#button-login'
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default class ViewUser extends Marionette.CollectionView
{
/** @ignore */
Radio.channel('rodan').on(RODAN_EVENTS.EVENT__USER_PREFERENCE_LOADED, (options) => this._handleUserPreferenceLoaded(options));
Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__MODAL_FORM_VALIDATION_ERROR, (options) => this._handleErrors(options));
}

/**
Expand All @@ -38,10 +39,14 @@ export default class ViewUser extends Marionette.CollectionView
*/
_handleButtonSave()
{
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__USER_SAVE,
{fields: {first_name: _.escape(this.ui.textFirstName.val()),
last_name: _.escape(this.ui.textLastName.val()),
email: _.escape(this.ui.textEmail.val())}});
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__USER_SAVE, {
fields: {
username: _.escape(this.ui.textUsername.val()),
first_name: _.escape(this.ui.textFirstName.val()),
last_name: _.escape(this.ui.textLastName.val()),
email: _.escape(this.ui.textEmail.val())
}
});
if (this._userPreference)
{
this._userPreference.set({'send_email': $(this.ui.checkboxSendEmail).prop('checked')});
Expand Down Expand Up @@ -86,16 +91,46 @@ export default class ViewUser extends Marionette.CollectionView
$(this.ui.divUserPreferenceLoading).show();
}
}

/**
* Handle errors.
*/
_handleErrors(options)
{
for (const [key, errors] of Object.entries(options.errors)) {
switch (key) {
case 'username':
$(this.ui.errorUsername).text(errors[0]);
break;
case 'first_name':
$(this.ui.errorFirstName).text(errors[0]);
break;
case 'last_name':
$(this.ui.errorLastName).text(errors[0]);
break;
case 'email':
$(this.ui.errorEmail).text(errors[0]);
break;
default:
break;
}
}
}
}
ViewUser.prototype.modelEvents = {
'all': 'render'
};
ViewUser.prototype.ui = {
buttonSave: '#button-save_user',
buttonPassword: '#button-change_password',
textUsername: '#text-user_username',
textFirstName: '#text-user_firstname',
textLastName: '#text-user_lastname',
textEmail: '#text-user_email',
errorUsername: '#error-user_username',
errorFirstName: '#error-user_firstname',
errorLastName: '#error-user_lastname',
errorEmail: '#error-user_email',
checkboxSendEmail: '#checkbox-send_email',
divUserPreference: '#div-user_preference',
divUserPreferenceLoading: '#div-user_preference_loading'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<div class="horizontal-center">
<form class="form-inline" role="form" action="javascript:void(0);">
<div class="form-group">
<label for="text-login_username">Username:</label>
<input type="text" class="form-control" id="text-login_username">
<label for="text-login_email">Email address:</label>
<input type="text" class="form-control" id="text-login_email">
</div>
<div class="form-group">
<label for="text-login_password">Password:</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
<div class="fixed_individual">
<label for="text-user_username">Username:</label>
<input type="text" class="form-control input-sm" id="text-user_username" value="<%= _.unescape(username) %>" name="text-user_username">
<p id="error-user_username" class="text-danger"></p>

<label for="text-user_firstname">First name:</label>
<input type="text" class="form-control input-sm" id="text-user_firstname" value="<%= _.unescape(first_name) %>" name="text-user_firstname">
<p id="error-user_firstname" class="text-danger"></p>

<label for="text-user_lastname">Last name:</label>
<input type="text" class="form-control input-sm" id="text-user_lastname" value="<%= _.unescape(last_name) %>" name="text-user_lastname">
<p id="error-user_lastname" class="text-danger"></p>

<label for="text-user_email">Email:</label>
<input inputmode="email" type="email" class="form-control input-sm" id="text-user_email" value="<%= _.unescape(email) %>" name="text-user_email">
<p id="error-user_email" class="text-danger"></p>

<div class="checkbox" id="div-user_preference">
<label><input id="checkbox-send_email" type="checkbox" value="">Receive Rodan notifications via email</label>
</div>
Expand Down
47 changes: 24 additions & 23 deletions rodan-main/code/rodan/admin/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from django.contrib import admin
from django.contrib.auth import forms
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from guardian.admin import GuardedModelAdmin

from rodan.models.project import Project

from rodan.models.user import User
from rodan.models.workflow import Workflow
from rodan.models.workflowjob import WorkflowJob
from rodan.models.workflowrun import WorkflowRun
Expand All @@ -12,27 +14,14 @@
from rodan.models.resource import Resource
from rodan.models.resourcelist import ResourceList

# from rodan.models.rodanuser import RodanUser


# class WorkflowJobInline(admin.StackedInline):
# model = WorkflowJob
# extra = 5


# class WorkflowAdmin(admin.ModelAdmin):
# inlines = [WorkflowJobInline]


class JobAdmin(admin.ModelAdmin):
list_display = ('name', 'enabled', 'category')

# @admin.register(Project)
class ProjectAdmin(GuardedModelAdmin):
list_display = ('name', 'uuid', 'creator', 'created', 'updated')
readonly_fields = ('uuid',)

# @admin.register(Workflow)
class WorkflowJobAdmin(admin.ModelAdmin):
list_display = ('job_name', 'created', 'updated')
list_filter = ('workflow__name',)
Expand All @@ -56,20 +45,31 @@ class ResourceAdmin(admin.ModelAdmin):
class ResourceListAdmin(admin.ModelAdmin):
list_display = ('created', 'updated')

class UserChangeForm(forms.UserChangeForm):
class Meta(forms.UserChangeForm.Meta):
model = User

class UserCreationForm(forms.UserCreationForm):
class Meta(forms.UserCreationForm.Meta):
model = User

# class UserProfileInline(admin.StackedInline):
# model = RodanUser
# can_delete = False
# verbose_name_plural = 'profile'
class UserAdmin(BaseUserAdmin):
form = UserChangeForm
add_form = UserCreationForm

list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser', 'is_active')

# class RodanUserAdmin(UserAdmin):
# inlines = (UserProfileInline, )
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'email', 'password1', 'password2'),
}),
)

# admin.site.unregister(User)
# admin.site.register(User, RodanUserAdmin)
search_fields = ('username', 'email')
ordering = ('username', 'email')

# admin.site.register(RodanUser)
admin.site.register(Project, ProjectAdmin)
admin.site.register(WorkflowRun, WorkflowRunAdmin)
admin.site.register(RunJob, RunJobAdmin)
Expand All @@ -79,3 +79,4 @@ class ResourceListAdmin(admin.ModelAdmin):
admin.site.register(ResultsPackage, ResultsPackageAdmin)
admin.site.register(Resource)
admin.site.register(ResourceList, ResourceListAdmin)
admin.site.register(User, UserAdmin)
34 changes: 29 additions & 5 deletions rodan-main/code/rodan/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2022-07-13 19:44
from __future__ import unicode_literals
# Generated by Django 2.0.13 on 2023-06-26 17:12

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import jsonfield.fields
import rodan.models.resource
import sortedm2m.fields
Expand All @@ -16,11 +17,34 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
('auth', '0008_alter_user_username_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('auth', '0009_alter_user_last_name_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')),
('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=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, 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')),
('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={
'db_table': 'auth_user',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='Connection',
fields=[
Expand Down
35 changes: 35 additions & 0 deletions rodan-main/code/rodan/migrations/0003_auto_20230627_1526.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django.db import migrations, models
import rodan.models.user


class Migration(migrations.Migration):
"""
This migration makes the email field unique and non-blank and adds permissions to custom user model.
"""

dependencies = [
('rodan', '0002_auto_20230522_1420'),
]

operations = [
migrations.AlterModelManagers(
name='user',
managers=[
('objects', rodan.models.user.UserManager()),
],
),
migrations.AlterModelOptions(
name='user',
options={'permissions': (('view_user', 'View User'),)},
),
# Make sure there are no empty email fields by giving them a default value
migrations.RunSQL(
sql="UPDATE auth_user SET email = CONCAT(username, '@rodan2.simssa.ca') WHERE email = ''",
reverse_sql="UPDATE auth_user SET email = '' WHERE email LIKE '%@rodan2.simssa.ca'"
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(error_messages={'unique': 'A user with that email already exists.'}, max_length=254, unique=True, verbose_name='email address'),
),
]
5 changes: 3 additions & 2 deletions rodan-main/code/rodan/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import subprocess

from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission, User, Group
from django.contrib.auth.models import Permission, Group
from django.db.models.signals import (
pre_migrate,
post_migrate,
Expand Down Expand Up @@ -41,6 +41,7 @@
from rodan.models.resourcetype import ResourceType
from rodan.models.connection import Connection
from rodan.models.tempauthtoken import Tempauthtoken
from rodan.models.user import User


if sys.version_info.major == 2:
Expand All @@ -57,7 +58,7 @@ def add_view_user_permission(sender, **kwargs):
"""
# don't set permissions in test database
if not settings.TEST and sender.name == 'guardian':
content_type = ContentType.objects.get(app_label='auth', model='user')
content_type = ContentType.objects.get(app_label='rodan', model='user')
Permission.objects.get_or_create(codename='view_user', name='View User', content_type=content_type)

group = Group.objects.get_or_create(name="view_user_permission")
Expand Down
3 changes: 2 additions & 1 deletion rodan-main/code/rodan/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import traceback
import uuid
from django.conf import settings
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import Group
from django.urls import reverse
from django.db import models
from rodan.models.user import User


logger = logging.getLogger("rodan")
Expand Down
Loading

0 comments on commit 47aa7d2

Please sign in to comment.