Merged in feature/profile-image (pull request #16)
Feature/profile image Approved-by: Ramon Wenger <ramon.wenger@iterativ.ch>
This commit is contained in:
commit
ed38e73f5b
|
|
@ -1,19 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user-widget" :class="{'user-widget--is-profile': isProfile}">
|
<div class="user-widget" :class="{'user-widget--is-profile': isProfile}">
|
||||||
<user-icon class="user-widget__avatar" :src="avatar"></user-icon>
|
<div class="user-widget__avatar">
|
||||||
|
<avatar :avatar-url="avatarUrl" :icon-highlighted="isProfile" />
|
||||||
|
</div>
|
||||||
<span class="user-widget__name">{{firstName}} {{lastName}}</span>
|
<span class="user-widget__name">{{firstName}} {{lastName}}</span>
|
||||||
<span class="user-widget__date" v-if="date">{{date}}</span>
|
<span class="user-widget__date" v-if="date">{{date}}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import UserIcon from '@/components/icons/UserIcon';
|
import Avatar from '@/components/profile/Avatar';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['firstName', 'lastName', 'avatar', 'date'],
|
props: ['firstName', 'lastName', 'avatarUrl', 'date'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
UserIcon
|
Avatar
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isProfile() {
|
isProfile() {
|
||||||
|
|
@ -44,15 +46,10 @@
|
||||||
&__avatar {
|
&__avatar {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border-radius: 15px;
|
|
||||||
fill: $color-grey;
|
fill: $color-grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--is-profile {
|
&--is-profile {
|
||||||
& > svg {
|
|
||||||
fill: $color-brand;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
color: $color-brand;
|
color: $color-brand;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="owner-widget">
|
<div class="owner-widget">
|
||||||
<user-icon></user-icon>
|
<avatar class="owner-widget__avatar" :avatarUrl="avatarUrl" />
|
||||||
<span>
|
<span>
|
||||||
{{name}}
|
{{name}}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -8,32 +8,34 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import UserIcon from '@/components/icons/UserIcon.vue';
|
import Avatar from '@/components/profile/Avatar';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['name'],
|
props: ['name', 'avatarUrl'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
UserIcon
|
Avatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "@/styles/_mixins.scss";
|
@import "@/styles/_mixins.scss";
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
|
||||||
.owner-widget {
|
.owner-widget {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
opacity: 0.6;
|
margin-top: $small-spacing;
|
||||||
|
|
||||||
svg {
|
&__avatar {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
fill: $color-darkgrey-1;
|
height: 30px;
|
||||||
margin-right: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
|
opacity: 0.6;
|
||||||
|
margin-left: $small-spacing;
|
||||||
@include room-widget-text-style;;
|
@include room-widget-text-style;;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<h3 class="project-widget__title">{{title}}</h3>
|
<h3 class="project-widget__title">{{title}}</h3>
|
||||||
|
|
||||||
<entry-count-widget :entry-count="entriesCount"></entry-count-widget>
|
<entry-count-widget :entry-count="entriesCount"></entry-count-widget>
|
||||||
<owner-widget :name="owner"></owner-widget>
|
<owner-widget :name="owner" :avatarUrl="student.avatarUrl"></owner-widget>
|
||||||
</router-link>
|
</router-link>
|
||||||
<widget-footer v-if="isOwner" class="project-widget__footer">
|
<widget-footer v-if="isOwner" class="project-widget__footer">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<div class="avatar">
|
||||||
|
<transition name="fade">
|
||||||
|
<user-icon v-show="!isAvatarLoaded" class="avatar__placeholder" :class="{'avatar__placeholder--highlighted': iconHighlighted}"></user-icon>
|
||||||
|
</transition>
|
||||||
|
<transition name="show">
|
||||||
|
<div v-show="isAvatarLoaded" class="avatar__image" ref="avatarImage" :style="{'background-image': `url(${this.avatarUrl})`}"></div>
|
||||||
|
</transition>
|
||||||
|
<img class="avatar__fake-image" :src="avatarUrl" ref="fakeImage"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import UserIcon from '@/components/icons/UserIcon';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['avatarUrl', 'iconHighlighted'],
|
||||||
|
components: {UserIcon},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isAvatarLoaded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.avatarUrl !== '') {
|
||||||
|
this.$refs.fakeImage.addEventListener('load', () => {
|
||||||
|
this.$refs.fakeImage.remove();
|
||||||
|
this.isAvatarLoaded = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
|
||||||
|
$max-width: 100%;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
height: $max-width;
|
||||||
|
width: $max-width;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&__placeholder {
|
||||||
|
height: $max-width;
|
||||||
|
fill: $color-grey;
|
||||||
|
|
||||||
|
&--highlighted {
|
||||||
|
fill: $color-brand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
&--landscape {
|
||||||
|
width: auto;
|
||||||
|
height: $max-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__fake-image {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-leave-active, .show-enter-active {
|
||||||
|
transition: opacity .5s;
|
||||||
|
}
|
||||||
|
.fade-leave-to, .show-enter {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div class="avatar-upload">
|
||||||
|
<image-form :value="value" index="0" @link-change-url="changeLinkUrl" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import ImageForm from '@/components/content-forms/ImageForm';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ImageForm
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeLinkUrl(value, index) {
|
||||||
|
this.$emit('avatarUpdate', value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: {
|
||||||
|
url: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="password-reset">
|
<div class="password-reset">
|
||||||
<h1 class="password-reset__header">Passwort ändern</h1>
|
<h2 class="password-reset__header">Passwort ändern</h2>
|
||||||
<div v-if="showSuccess" class="success-message">
|
<div v-if="showSuccess" class="success-message">
|
||||||
<p class="success-message__msg" data-cy="password-change-success">Dein Password wurde erfolgreich geändert.</p>
|
<p class="success-message__msg" data-cy="password-change-success">Dein Password wurde erfolgreich geändert.</p>
|
||||||
</div>
|
</div>
|
||||||
<password-change
|
<password-change-form
|
||||||
@passwordSubmited="resetPassword"
|
@passwordSubmited="resetPassword"
|
||||||
:oldPasswordErrors="oldPasswordErrors"
|
:oldPasswordErrors="oldPasswordErrors"
|
||||||
:newPasswordErrors="newPasswordErrors" />
|
:newPasswordErrors="newPasswordErrors" />
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import UPDATE_PASSWORD_MUTATION from '@/graphql/gql/mutations/updatePassword.gql';
|
import UPDATE_PASSWORD_MUTATION from '@/graphql/gql/mutations/updatePassword.gql';
|
||||||
import PasswordChange from '@/components/PasswordChange'
|
import PasswordChangeForm from '@/components/profile/PasswordChangeForm'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
PasswordChange
|
PasswordChangeForm
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
<template>
|
||||||
|
<div class="profile">
|
||||||
|
<h1 class="profile__header">Profil</h1>
|
||||||
|
<h2 class="profile__avatar profile-avatar">Profilbild</h2>
|
||||||
|
<div class="profile-avatar" v-if="me.avatarUrl" >
|
||||||
|
<div class="profile-avatar__image">
|
||||||
|
<avatar :avatarUrl="me.avatarUrl" />
|
||||||
|
</div>
|
||||||
|
<a class="profile-avatar__remove icon-button" @click="deleteAvatar()">
|
||||||
|
<trash-icon class="profile-avatar__remove-icon icon-button__icon"></trash-icon>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<avatar-upload-form v-else @avatarUpdate="updateAvatar"/>
|
||||||
|
<password-change />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import UPDATE_AVATAR_QUERY from '@/graphql/gql/mutations/UpdateAvatarUrl.gql';
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
import PasswordChange from '@/components/profile/PasswordChange';
|
||||||
|
import AvatarUploadForm from '@/components/profile/AvatarUploadForm';
|
||||||
|
import Avatar from '@/components/profile/Avatar';
|
||||||
|
import TrashIcon from '@/components/icons/TrashIcon';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PasswordChange,
|
||||||
|
AvatarUploadForm,
|
||||||
|
Avatar,
|
||||||
|
TrashIcon
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
me: {
|
||||||
|
avatarUrl: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
me: {
|
||||||
|
query: ME_QUERY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleteAvatar () {
|
||||||
|
this.updateAvatar('');
|
||||||
|
},
|
||||||
|
updateAvatar (url) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: UPDATE_AVATAR_QUERY,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
avatarUrl: url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(store, {data: {updateAvatar: {success}}}) {
|
||||||
|
if (success) {
|
||||||
|
const data = store.readQuery({query: ME_QUERY});
|
||||||
|
if (data) {
|
||||||
|
data.me.avatarUrl = url;
|
||||||
|
store.writeQuery({query: ME_QUERY, data});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
console.warn('UploadError', error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
height: 230px;
|
||||||
|
width: 230px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -9,7 +9,8 @@ fragment ProjectParts on ProjectNode {
|
||||||
student {
|
student {
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
id
|
id,
|
||||||
|
avatarUrl
|
||||||
}
|
}
|
||||||
entriesCount
|
entriesCount
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,6 @@ fragment RoomEntryParts on RoomEntryNode {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
|
avatarUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ fragment UserParts on UserNode {
|
||||||
email
|
email
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
avatar
|
avatarUrl
|
||||||
lastModule {
|
lastModule {
|
||||||
id
|
id
|
||||||
slug
|
slug
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
mutation UpdateAvatar($input: UpdateAvatarInput!) {
|
||||||
|
updateAvatar(input: $input) {
|
||||||
|
success
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
errors {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,28 +7,14 @@
|
||||||
<router-link to="/me/myclasses" active-class="top-navigation__link--active"
|
<router-link to="/me/myclasses" active-class="top-navigation__link--active"
|
||||||
class="top-navigation__link profile-submenu__item submenu-item">Klassenliste
|
class="top-navigation__link profile-submenu__item submenu-item">Klassenliste
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/me/password-change" active-class="top-navigation__link--active"
|
<router-link to="/me/profile" active-class="top-navigation__link--active"
|
||||||
class="top-navigation__link profile-submenu__item submenu-item">Passwort ändern
|
class="top-navigation__link profile-submenu__item submenu-item">Profil
|
||||||
</router-link>
|
</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
me: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "@/styles/_variables.scss";
|
@import "@/styles/_variables.scss";
|
||||||
@import "@/styles/_functions.scss";
|
@import "@/styles/_functions.scss";
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import submission from '@/pages/studentSubmission'
|
||||||
import portfolio from '@/pages/portfolio'
|
import portfolio from '@/pages/portfolio'
|
||||||
import project from '@/pages/project'
|
import project from '@/pages/project'
|
||||||
import profilePage from '@/pages/profile'
|
import profilePage from '@/pages/profile'
|
||||||
import passwordChange from '@/pages/passwordChange'
|
import profile from '@/components/profile/profile'
|
||||||
import myClasses from '@/pages/myClasses'
|
import myClasses from '@/pages/myClasses'
|
||||||
import activity from '@/pages/activity'
|
import activity from '@/pages/activity'
|
||||||
import Router from 'vue-router'
|
import Router from 'vue-router'
|
||||||
|
|
@ -81,7 +81,7 @@ const routes = [
|
||||||
path: '/me',
|
path: '/me',
|
||||||
component: profilePage,
|
component: profilePage,
|
||||||
children: [
|
children: [
|
||||||
{path: 'password-change', name: 'pw-change', component: passwordChange, meta: {isProfile: true}},
|
{path: 'profile', name: 'profile', component: profile, meta: {isProfile: true}},
|
||||||
{path: 'myclasses', name: 'my-classes', component: myClasses, meta: {isProfile: true}},
|
{path: 'myclasses', name: 'my-classes', component: myClasses, meta: {isProfile: true}},
|
||||||
{path: 'activity', name: 'activity', component: activity, meta: {isProfile: true}},
|
{path: 'activity', name: 'activity', component: activity, meta: {isProfile: true}},
|
||||||
{path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}},
|
{path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.0.6 on 2019-04-23 12:08
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0003_user_last_module'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='avatar_url',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=254),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -12,6 +12,7 @@ DEFAULT_SCHOOL_ID = 1
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True)
|
last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True)
|
||||||
|
avatar_url = models.CharField(max_length=254, blank=True, default='')
|
||||||
|
|
||||||
def get_role_permissions(self):
|
def get_role_permissions(self):
|
||||||
perms = set()
|
perms = set()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import graphene
|
||||||
from django.contrib.auth import update_session_auth_hash
|
from django.contrib.auth import update_session_auth_hash
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
from users.inputs import PasswordUpdateInput
|
from users.inputs import PasswordUpdateInput
|
||||||
from users.serializers import PasswordSerialzer
|
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
||||||
|
|
||||||
|
|
||||||
class FieldError(graphene.ObjectType):
|
class FieldError(graphene.ObjectType):
|
||||||
|
|
@ -19,7 +19,7 @@ class UpdatePassword(relay.ClientIDMutation):
|
||||||
password_input = graphene.Argument(PasswordUpdateInput)
|
password_input = graphene.Argument(PasswordUpdateInput)
|
||||||
|
|
||||||
success = graphene.Boolean()
|
success = graphene.Boolean()
|
||||||
errors = graphene.List(UpdateError)
|
errors = graphene.List(UpdateError) # todo: change for consistency
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
|
@ -45,5 +45,38 @@ class UpdatePassword(relay.ClientIDMutation):
|
||||||
return cls(success=False, errors=errors)
|
return cls(success=False, errors=errors)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAvatar(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
avatar_url = graphene.String()
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
errors = graphene.List(UpdateError) # todo: change for consistency
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
user = info.context.user
|
||||||
|
avatar_data = kwargs.get('avatar_url')
|
||||||
|
|
||||||
|
serializer = AvatarUrlSerializer(data={'avatar_url': avatar_data})
|
||||||
|
if serializer.is_valid():
|
||||||
|
user.avatar_url = avatar_data
|
||||||
|
user.save()
|
||||||
|
return cls(success=True)
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
for key, value in serializer.errors.items():
|
||||||
|
|
||||||
|
error = UpdateError(field=key, errors=[])
|
||||||
|
|
||||||
|
for field_error in serializer.errors[key]:
|
||||||
|
error.errors.append(FieldError(code=field_error.code))
|
||||||
|
|
||||||
|
errors.append(error)
|
||||||
|
|
||||||
|
return cls(success=False, errors=errors)
|
||||||
|
|
||||||
|
|
||||||
class ProfileMutations:
|
class ProfileMutations:
|
||||||
update_password = UpdatePassword.Field()
|
update_password = UpdatePassword.Field()
|
||||||
|
update_avatar = UpdateAvatar.Field()
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -11,7 +11,7 @@ import re
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField, URLField
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
MIN_PASSWORD_LENGTH = 8
|
MIN_PASSWORD_LENGTH = 8
|
||||||
|
|
@ -63,3 +63,7 @@ class PasswordSerialzer(serializers.Serializer):
|
||||||
|
|
||||||
def validate(self, obj):
|
def validate(self, obj):
|
||||||
return validate_old_new_password(obj)
|
return validate_old_new_password(obj)
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarUrlSerializer(serializers.Serializer):
|
||||||
|
avatar_url = URLField(allow_blank=True)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue