Feature/personal profiletags/v2.1.4
@@ -9,7 +9,7 @@ | |||
}, | |||
"dependencies": { | |||
"axios": "^0.19.0", | |||
"buefy": "^0.8.8", | |||
"buefy": "^0.8.20", | |||
"core-js": "^3.3.2", | |||
"register-service-worker": "^1.6.2", | |||
"vue": "^2.6.10", | |||
@@ -0,0 +1,137 @@ | |||
<template> | |||
<div class="user-profile-card"> | |||
<div id="user-home-container"> | |||
<div class="card"> | |||
<div class="card-content"> | |||
<div class="media"> | |||
<div class="media-left"> | |||
<figure class="image is-48x48"> | |||
<b-skeleton width="48px" height="48px" :active="avatarLoading"></b-skeleton> | |||
<img | |||
@load="onAvatarLoaded" | |||
v-show="!avatarLoading" | |||
:src="user.avatar" | |||
alt="avatar" | |||
> | |||
</figure> | |||
</div> | |||
<div class="media-content" v-show="!avatarLoading"> | |||
<p class="title is-4">{{ user.username }}</p> | |||
<p class="subtitle is-6">@{{ location }}</p> | |||
</div> | |||
</div> | |||
<div class="content"> | |||
Yet another Pinry user. | |||
<br> | |||
</div> | |||
<div class="tabs is-toggle"> | |||
<ul> | |||
<li :class="trueFalse2Class(inPins)"> | |||
<a @click="go2UserPins"> | |||
<b-icon | |||
type="is-dark" | |||
icon="image" | |||
custom-size="mdi-24px"> | |||
</b-icon> | |||
<span>Pins</span> | |||
</a> | |||
</li> | |||
<li :class="trueFalse2Class(inBoard)"> | |||
<a @click="go2UserBoard"> | |||
<b-icon | |||
type="is-dark" | |||
icon="folder-multiple-image" | |||
custom-size="mdi-24px"> | |||
</b-icon> | |||
<span>Boards</span> | |||
</a> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import api from './api'; | |||
export default { | |||
name: 'UserProfileCard.vue', | |||
props: { | |||
username: String, | |||
inBoard: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
inPins: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
}, | |||
data() { | |||
return { | |||
location: window.location.host, | |||
avatarLoading: true, | |||
user: { | |||
avatar: '', | |||
username: '', | |||
}, | |||
}; | |||
}, | |||
beforeMount() { | |||
this.initializeUser(this.username); | |||
}, | |||
methods: { | |||
go2UserBoard() { | |||
this.$router.push( | |||
{ name: 'boards4user', params: { username: this.username } }, | |||
); | |||
}, | |||
go2UserPins() { | |||
this.$router.push( | |||
{ name: 'user', params: { user: this.username } }, | |||
); | |||
}, | |||
trueFalse2Class(boolValue) { | |||
if (boolValue) { | |||
return 'is-active'; | |||
} | |||
return ''; | |||
}, | |||
onAvatarLoaded() { | |||
this.avatarLoading = false; | |||
}, | |||
initializeUser(username) { | |||
const self = this; | |||
api.User.fetchUserInfoByName(username).then( | |||
(user) => { | |||
if (user === null) { | |||
self.$router.push( | |||
{ name: 'PageNotFound' }, | |||
); | |||
} else { | |||
self.user.avatar = `//gravatar.com/avatar/${user.gravatar}`; | |||
self.user.username = user.username; | |||
self.user.meta = user; | |||
} | |||
}, | |||
); | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
#user-home-container { | |||
margin-top: 2rem; | |||
margin-left: auto; | |||
margin-right: auto; | |||
box-shadow: 5px 5px 2px 1px rgba(0, 0, 255, .1); | |||
} | |||
@import '../components/utils/grid-layout'; | |||
@include screen-grid-layout("#user-home-container"); | |||
</style> |
@@ -238,6 +238,23 @@ const User = { | |||
}, | |||
); | |||
}, | |||
fetchUserInfoByName(username) { | |||
/* returns null if user not logged in */ | |||
const url = `${API_PREFIX}profile/public-users/?username=${username}`; | |||
return new Promise( | |||
(resolve) => { | |||
axios.get(url).then( | |||
(resp) => { | |||
const users = resp.data; | |||
if (users.length === 0) { | |||
return resolve(null); | |||
} | |||
return resolve(users[0]); | |||
}, | |||
); | |||
}, | |||
); | |||
}, | |||
fetchUserInfo(force = false) { | |||
/* returns null if user not logged in */ | |||
const self = this; | |||
@@ -1,12 +1,14 @@ | |||
<template> | |||
<div class="boards-for-user"> | |||
<PHeader></PHeader> | |||
<UserProfileCard :in-board="true" :username="filters.boardUsername"></UserProfileCard> | |||
<Boards :filters="filters"></Boards> | |||
</div> | |||
</template> | |||
<script> | |||
import PHeader from '../components/PHeader.vue'; | |||
import UserProfileCard from '../components/UserProfileCard.vue'; | |||
import Boards from '../components/Boards.vue'; | |||
export default { | |||
@@ -18,6 +20,7 @@ export default { | |||
}, | |||
components: { | |||
PHeader, | |||
UserProfileCard, | |||
Boards, | |||
}, | |||
created() { | |||
@@ -1,12 +1,14 @@ | |||
<template> | |||
<div class="pins-for-user"> | |||
<PHeader></PHeader> | |||
<UserProfileCard :in-pins="true" :username="filters.userFilter"></UserProfileCard> | |||
<Pins :pin-filters="filters"></Pins> | |||
</div> | |||
</template> | |||
<script> | |||
import PHeader from '../components/PHeader.vue'; | |||
import UserProfileCard from '../components/UserProfileCard.vue'; | |||
import Pins from '../components/Pins.vue'; | |||
export default { | |||
@@ -18,6 +20,7 @@ export default { | |||
}, | |||
components: { | |||
PHeader, | |||
UserProfileCard, | |||
Pins, | |||
}, | |||
created() { | |||
@@ -1602,9 +1602,9 @@ browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.7.3, browserslist@^4.8 | |||
electron-to-chromium "^1.3.322" | |||
node-releases "^1.1.42" | |||
buefy@^0.8.8: | |||
version "0.8.8" | |||
resolved "https://registry.npm.taobao.org/buefy/download/buefy-0.8.8.tgz#dc6a26c74793a6e28ee435d5ee06bdfe811dc9f4" | |||
buefy@^0.8.20: | |||
version "0.8.20" | |||
resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.8.20.tgz#75708800548220654575903d031a81fc8575b7f3" | |||
dependencies: | |||
bulma "0.7.5" | |||
@@ -6,6 +6,21 @@ from rest_framework.exceptions import ValidationError | |||
from users.models import User | |||
class PublicUserSerializer(serializers.HyperlinkedModelSerializer): | |||
class Meta: | |||
model = User | |||
fields = ( | |||
'username', | |||
'gravatar', | |||
settings.DRF_URL_FIELD_NAME, | |||
) | |||
extra_kwargs = { | |||
settings.DRF_URL_FIELD_NAME: { | |||
"view_name": "public-users:user-detail", | |||
}, | |||
} | |||
class UserSerializer(serializers.HyperlinkedModelSerializer): | |||
class Meta: | |||
model = User | |||
@@ -7,6 +7,7 @@ from django.contrib.auth.decorators import login_required | |||
from django.http import HttpResponseRedirect, HttpResponseBadRequest, HttpResponse | |||
from django.urls import reverse | |||
from django.utils.functional import lazy | |||
from django_filters.rest_framework import DjangoFilterBackend | |||
from rest_framework import mixins, routers | |||
from rest_framework.permissions import BasePermission | |||
from rest_framework.renderers import JSONRenderer | |||
@@ -20,6 +21,21 @@ def reverse_lazy(name=None, *args): | |||
return lazy(reverse, str)(name, args=args) | |||
class PublicUserViewSet( | |||
mixins.RetrieveModelMixin, | |||
mixins.ListModelMixin, | |||
GenericViewSet, | |||
): | |||
serializer_class = UserSerializer | |||
filter_backends = (DjangoFilterBackend, ) | |||
filter_fields = ("username", ) | |||
pagination_class = None | |||
def get_queryset(self): | |||
username = self.request.GET.get("username", "") | |||
return User.objects.filter(username=username) | |||
class UserViewSet( | |||
mixins.RetrieveModelMixin, | |||
mixins.ListModelMixin, | |||
@@ -87,3 +103,4 @@ def logout_user(request): | |||
drf_router = routers.DefaultRouter() | |||
drf_router.register(r'users', UserViewSet, basename="user") | |||
drf_router.register(r'public-users', PublicUserViewSet, basename="public-user") |