Skip to content

Commit

Permalink
AIRAVATA-3319 Simple user profile editor for editing first name, last…
Browse files Browse the repository at this point in the history
… name
  • Loading branch information
machristie committed Apr 21, 2021
1 parent eafe669 commit e2d5121
Show file tree
Hide file tree
Showing 18 changed files with 9,026 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ package-lock.json
# mkdocs
site/
.tox/
yarn-error.log
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ const services = {
"UnverifiedEmailUsers"
),
UserProfileService: ServiceFactory.service("UserProfiles"),
UserService: ServiceFactory.service("Users"),
UserStoragePathService: ServiceFactory.service("UserStoragePaths"),
WorkspacePreferencesService: ServiceFactory.service("WorkspacePreferences"),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import BaseModel from "./BaseModel";

const FIELDS = ["id", "username", "first_name", "last_name", "email"];

export default class User extends BaseModel {
constructor(data = {}) {
super(FIELDS, data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import SharedEntity from "./models/SharedEntity";
import StoragePreference from "./models/StoragePreference";
import StorageResourceDescription from "./models/StorageResourceDescription";
import UnverifiedEmailUserProfile from "./models/UnverifiedEmailUserProfile";
import User from "./models/User";
import UserProfile from "./models/UserProfile";
import UserStoragePath from "./models/UserStoragePath";
import WorkspacePreferences from "./models/WorkspacePreferences";
Expand Down Expand Up @@ -369,6 +370,17 @@ export default {
queryParams: ["limit", "offset"],
modelClass: UnverifiedEmailUserProfile,
},
Users: {
url: "/auth/users",
viewSet: true,
methods: {
current: {
url: "/auth/users/current/",
requestType: "get"
}
},
modelClass: User,
},
UserProfiles: {
url: "/api/user-profiles",
viewSet: ["list", "retrieve"],
Expand Down
2 changes: 2 additions & 0 deletions django_airavata/apps/auth/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
templates
3 changes: 3 additions & 0 deletions django_airavata/apps/auth/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/app"]
};
55 changes: 55 additions & 0 deletions django_airavata/apps/auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "django-airavata-auth-views",
"description": "A Vue.js project",
"version": "1.0.0",
"author": "Apache Airavata <[email protected]>",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"format": "prettier --write ."
},
"dependencies": {
"bootstrap": "^4.0.0-beta.2",
"bootstrap-vue": "2.0.0-rc.26",
"django-airavata-api": "link:../api/",
"django-airavata-common-ui": "link:../../static/common/",
"vue": "^2.5.21"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.1.1",
"@vue/cli-plugin-eslint": "^3.1.1",
"@vue/cli-service": "^3.1.1",
"babel-eslint": "^10.0.1",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0-0",
"prettier": "^2.1.2",
"vue-template-compiler": "^2.5.21",
"webpack-bundle-tracker": "^0.4.2-beta"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
6 changes: 6 additions & 0 deletions django_airavata/apps/auth/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['id', 'username', 'first_name', 'last_name', 'email']

def update(self, instance, validated_data):
instance.first_name = validated_data['first_name']
instance.last_name = validated_data['last_name']
instance.save()
return instance
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<b-card>
<b-form-group label="Username">
<b-form-input disabled :value="user.username" />
</b-form-group>
<b-form-group label="First Name">
<b-form-input v-model="user.first_name" />
</b-form-group>
<b-form-group label="Last Name">
<b-form-input v-model="user.last_name" />
</b-form-group>
<b-form-group label="Email">
<b-form-input disabled :value="user.email" />
</b-form-group>
<b-button variant="primary" @click="$emit('save', user)">Save</b-button>
<b-button>Cancel</b-button>
</b-card>
</template>

<script>
import { models } from "django-airavata-api";
export default {
name: "user-profile-editor",
props: {
value: {
type: models.User,
required: true,
},
},
data() {
return {
user: this.cloneValue(),
};
},
methods: {
cloneValue() {
return JSON.parse(JSON.stringify(this.value));
},
},
};
</script>

<style></style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<template>
<div>
<h1 class="h4 mb-4">User Profile Editor</h1>
<user-profile-editor v-if="user" v-model="user" @save="onSave" />
</div>
</template>

<script>
import { services } from "django-airavata-api";
import UserProfileEditor from "../components/UserProfileEditor.vue";
export default {
components: { UserProfileEditor },
name: "user-profile-container",
created() {
services.UserService.current().then((user) => {
this.user = user;
});
},
data() {
return {
user: null,
};
},
methods: {
onSave(value) {
services.UserService.update({
lookup: value.id,
data: value,
}).then((user) => {
this.user = user;
});
},
},
};
</script>

<style></style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { components, entry } from "django-airavata-common-ui";
import UserProfileContainer from "./containers/UserProfileContainer.vue";

entry(Vue => {
new Vue({
render: h => h(components.MainLayout, [h(UserProfileContainer)])
}).$mount("#user-profile");
});
23 changes: 23 additions & 0 deletions django_airavata/apps/auth/templates/django_airavata_auth/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends 'base.html' %}

{% load static %}
{% load render_bundle from webpack_loader %}

{% block css %}
{% render_bundle 'chunk-vendors' 'css' 'AUTH' %}
{% comment %}BUT NOTE: if you only have one entry point you won't have a 'chunk-common' bundle so you may need to comment out the next line until you have more than one entry point.{% endcomment %}
{% comment %} {% render_bundle 'chunk-common' 'css' 'MYAPP' %} {% endcomment %}
{% render_bundle bundle_name 'css' 'AUTH' %}
{% endblock %}

{% block content %}
<div id="{{ bundle_name }}"/>
{% endblock %}


{% block scripts %}
{% render_bundle 'chunk-vendors' 'js' 'AUTH' %}
{% comment %}BUT NOTE: if you only have one entry point you won't have a 'chunk-common' bundle so you may need to comment out the next line until you have more than one entry point.{% endcomment %}
{% comment %} {% render_bundle 'chunk-common' 'js' 'MYAPP' %} {% endcomment %}
{% render_bundle bundle_name 'js' 'AUTH' %}
{% endblock %}
1 change: 1 addition & 0 deletions django_airavata/apps/auth/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@
views.login_desktop_success, name="login_desktop_success"),
url(r'^refreshed-token-desktop$', views.refreshed_token_desktop,
name="refreshed_token_desktop"),
url(r'^user-profile/', views.user_profile),
]
20 changes: 17 additions & 3 deletions django_airavata/apps/auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import authenticate, get_user_model, login, logout
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.forms import ValidationError
from django.http import HttpResponseBadRequest, JsonResponse
Expand All @@ -15,6 +16,7 @@
from django.views.decorators.debug import sensitive_variables
from requests_oauthlib import OAuth2Session
from rest_framework import permissions, viewsets
from rest_framework.decorators import action

from django_airavata.apps.auth import serializers

Expand Down Expand Up @@ -507,25 +509,37 @@ def _create_login_desktop_failed_response(request, idp_alias=None):
"?" + urlencode(params))


class IsUserOrReadOnlyForSuperuser(permissions.BasePermission):
@login_required
def user_profile(request):
return render(request, "django_airavata_auth/base.html", {
'bundle_name': "user-profile"
})


class IsUserOrReadOnlyForAdmins(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.is_authenticated

def has_object_permission(self, request, view, obj):
if (request.method in permissions.SAFE_METHODS and
request.user.is_superuser):
request.is_gateway_admin):
return True
return obj == request.user


# TODO: disable deleting and creating?
class UserViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserSerializer
queryset = get_user_model().objects.all()
permission_classes = [IsUserOrReadOnlyForSuperuser]
permission_classes = [IsUserOrReadOnlyForAdmins]

def get_queryset(self):
user = self.request.user
if user.is_superuser:
return get_user_model().objects.all()
else:
return get_user_model().objects.get(pk=user.pk)

@action(detail=False)
def current(self, request):
return redirect(reverse('django_airavata_auth:user-detail', kwargs={'pk': request.user.id}))
80 changes: 80 additions & 0 deletions django_airavata/apps/auth/vue.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const BundleTracker = require("webpack-bundle-tracker");
const path = require("path");

module.exports = {
publicPath:
process.env.NODE_ENV === "development"
? "http://localhost:9000/static/django_airavata_auth/dist/"
: "/static/django_airavata_auth/dist/",
outputDir: "./static/django_airavata_auth/dist",
pages: {
"user-profile": "./static/django_airavata_auth/js/entry-user-profile"
// additional entry points go here ...
},
css: {
loaderOptions: {
postcss: {
config: {
path: __dirname
}
}
}
},
configureWebpack: {
plugins: [
new BundleTracker({
filename: "webpack-stats.json",
path: "./static/django_airavata_auth/dist/"
})
],
optimization: {
/*
* Force creating a vendor bundle so we can load the 'app' and 'vendor'
* bundles on development as well as production using django-webpack-loader.
* Otherwise there is no vendor bundle on development and we would need
* some template logic to skip trying to load it.
* See also: https://bitbucket.org/calidae/dejavu/src/d63d10b0030a951c3cafa6b574dad25b3bef3fe9/%7B%7Bcookiecutter.project_slug%7D%7D/frontend/vue.config.js?at=master&fileviewer=file-view-default#vue.config.js-27
*/
splitChunks: {
cacheGroups: {
vendors: {
name: "chunk-vendors",
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: "initial"
},
common: {
name: "chunk-common",
minChunks: 2,
priority: -20,
chunks: "initial",
reuseExistingChunk: true
}
}
}
}
},
chainWebpack: config => {
/*
* Specify the eslint config file otherwise it complains of a missing
* config file for the ../api and ../../static/common packages
*
* See: https://github.com/vuejs/vue-cli/issues/2539#issuecomment-422295246
*/
config.module
.rule("eslint")
.use("eslint-loader")
.tap(options => {
options.configFile = path.resolve(__dirname, "package.json");
return options;
});
},
devServer: {
port: 9000,
headers: {
"Access-Control-Allow-Origin": "*"
},
hot: true,
hotOnly: true
}
};
Loading

0 comments on commit e2d5121

Please sign in to comment.