-
-
Notifications
You must be signed in to change notification settings - Fork 776
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Users list #4011
Users list #4011
Changes from 31 commits
20a4793
dd1c620
b0d47a9
1d637fb
90c8b2a
5f43187
2517250
6678ec6
c5db4b0
1dccf15
16950a3
f274021
ed22557
2211791
0cea1d4
21a0d1f
cf58dbb
8c4f877
bf56fd8
9c8f345
aef15a7
23b2abe
082942d
752ffc8
a6414a7
7017636
5ff6d7f
2dc8aca
080a229
f380e1b
7b53c57
e2b1a45
cfec57c
7c9bae3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
let users = []; | ||
let usersPage = 1; | ||
let usersNumPages = ''; | ||
let usersHasNext = false; | ||
let numUsers = ''; | ||
|
||
Vue.mixin({ | ||
methods: { | ||
fetchUsers: function(newPage) { | ||
var vm = this; | ||
|
||
if (newPage) { | ||
vm.usersPage = newPage; | ||
} | ||
vm.params.page = vm.usersPage; | ||
|
||
if (vm.searchTerm) { | ||
vm.params.search = vm.searchTerm; | ||
} else { | ||
delete vm.params['search']; | ||
} | ||
let searchParams = new URLSearchParams(vm.params); | ||
|
||
let apiUrlUsers = `/api/v0.1/users_fetch/?${searchParams.toString()}`; | ||
|
||
var getUsers = fetchData (apiUrlUsers, 'GET'); | ||
|
||
$.when(getUsers).then(function(response) { | ||
|
||
response.data.forEach(function(item) { | ||
vm.users.push(item); | ||
}); | ||
|
||
vm.usersNumPages = response.num_pages; | ||
vm.usersHasNext = response.has_next; | ||
vm.numUsers = response.count; | ||
|
||
if (vm.usersHasNext) { | ||
vm.usersPage = ++vm.usersPage; | ||
|
||
} else { | ||
vm.usersPage = 1; | ||
} | ||
}); | ||
}, | ||
searchUsers: function() { | ||
vm = this; | ||
vm.users = []; | ||
|
||
vm.fetchUsers(1); | ||
|
||
}, | ||
bottomVisible: function() { | ||
vm = this; | ||
|
||
const scrollY = window.scrollY; | ||
const visible = document.documentElement.clientHeight; | ||
const pageHeight = document.documentElement.scrollHeight - 500; | ||
const bottomOfPage = visible + scrollY >= pageHeight; | ||
|
||
if (bottomOfPage || pageHeight < visible) { | ||
if (vm.usersHasNext) { | ||
vm.fetchUsers(); | ||
vm.usersHasNext = false; | ||
} | ||
} | ||
} | ||
} | ||
|
||
}); | ||
|
||
if (document.getElementById('gc-users-directory')) { | ||
var app = new Vue({ | ||
delimiters: [ '[[', ']]' ], | ||
el: '#gc-users-directory', | ||
data: { | ||
users, | ||
usersPage, | ||
usersNumPages, | ||
usersHasNext, | ||
numUsers, | ||
media_url, | ||
searchTerm: null, | ||
bottom: false, | ||
params: {} | ||
}, | ||
mounted() { | ||
this.fetchUsers(); | ||
}, | ||
beforeMount() { | ||
window.addEventListener('scroll', () => { | ||
this.bottom = this.bottomVisible(); | ||
}, false); | ||
}, | ||
beforeDestroy() { | ||
window.removeEventListener('scroll', () => { | ||
this.bottom = this.bottomVisible(); | ||
}); | ||
} | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
{% comment %} | ||
Copyright (C) 2019 Gitcoin Core | ||
|
||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU Affero General Public License as published | ||
by the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
|
||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU Affero General Public License for more details. | ||
|
||
You should have received a copy of the GNU Affero General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
{% endcomment %} | ||
{% load i18n static email_obfuscator add_url_schema avatar_tags %} | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
{% include 'shared/head.html' %} | ||
{% include 'shared/cards.html' %} | ||
|
||
<style> | ||
.grid-4 { | ||
display: grid; | ||
grid-template-columns: repeat(1, 1fr);; | ||
grid-gap: 5rem 3rem; | ||
} | ||
|
||
@media (min-width: 768px) { | ||
.grid-4 { | ||
grid-template-columns: repeat(2, 1fr);; | ||
} | ||
} | ||
|
||
@media (min-width: 992px) { | ||
.grid-4 { | ||
grid-template-columns: repeat(3, 1fr);; | ||
} | ||
} | ||
|
||
@media (min-width: 1200px) { | ||
.grid-4 { | ||
grid-template-columns: repeat(4, 1fr);; | ||
} | ||
} | ||
|
||
</style> | ||
|
||
</head> | ||
|
||
<body class="interior {{active}} g-font-muli bg-lightblue"> | ||
{% include 'shared/tag_manager_2.html' %} | ||
<div class="container-fluid header dash"> | ||
{% include 'shared/top_nav.html' with class='d-md-flex' %} | ||
{% include 'shared/nav.html' %} | ||
</div> | ||
<div class="" id="gc-users-directory" v-cloak> | ||
|
||
<div class="container-fluid"> | ||
<nav class="navbar navbar-expand-lg navbar-light bg-light"> | ||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#usersFilters" aria-controls="usersFilters" aria-expanded="false" aria-label="Toggle navigation"> | ||
<span class="navbar-toggler-icon"></span> | ||
</button> | ||
|
||
<div class="collapse navbar-collapse" id="usersFilters"> | ||
<!-- <div class="navbar-nav mr-4"> | ||
<label for="">{% trans 'Sort by:' %}</label> | ||
<select name="" id="sort_order"> | ||
<option value="-created_on">{% trans 'Newest' %}</option> | ||
<option value="created_on">{% trans 'Oldest' %}</option> | ||
<option value="-popularity">{% trans 'All Time Popularity' %}</option> | ||
<option value="-popularity_week">{% trans 'Trending' %}</option> | ||
<option value="price_finney">{% trans 'Low price' %}</option> | ||
<option value="-price_finney">{% trans 'High price' %}</option> | ||
<option value="name">{% trans 'Name' %}</option> | ||
<option value="-rarity">{% trans 'Rarity' %}</option> | ||
</select> | ||
|
||
</div> | ||
<div class="navbar-nav"> | ||
<select class="keywords-filter form-control" multiple="multiple" style="width:300px; height: 37px;"></select> | ||
</div> | ||
<ul class="navbar-nav mr-auto"> | ||
<li class="nav-item active"> | ||
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="#">Link</a> | ||
</li> | ||
<li class="nav-item dropdown"> | ||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||
Dropdown | ||
</a> | ||
<div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||
<a class="dropdown-item" href="#">Action</a> | ||
<a class="dropdown-item" href="#">Another action</a> | ||
<div class="dropdown-divider"></div> | ||
<a class="dropdown-item" href="#">Something else here</a> | ||
</div> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a> | ||
</li> | ||
</ul> --> | ||
<form class="form-inline my-2 my-lg-0"> | ||
<input class="form-control mr-sm-2" v-model="searchTerm" type="search" placeholder="Search" aria-label="Search"> | ||
<button class="btn btn-outline-success my-2 my-sm-0" @click.prevent="searchUsers()">Search</button> | ||
</form> | ||
</div> | ||
</nav> | ||
</div> | ||
<div class="container" @scroll.passive="onScroll($event)"> | ||
<div class="py-5 mt-5"> | ||
<div class="grid-4"> | ||
<div v-for="user in users" class="card shadow-sm border-0" :key="user.id"> | ||
<img class="rounded-circle shadow mx-auto mt-n5" width="140" height="140" :src="`/dynamic/avatar/${ user.handle }`"/> | ||
<div class="card-body"> | ||
<h5 class="text-center"> | ||
[[ user.data.name || user.handle ]] | ||
<span v-if="user.verification"> | ||
<img src="{% static 'v2/images/badge-verify.svg' %}" alt=""> | ||
</span> | ||
</h5> | ||
<a :href="`/profile/${ user.handle }`" class="text-center d-block"> | ||
@[[ user.handle ]] | ||
</a> | ||
<div v-if="user.keywords"> | ||
<template v-for="keyword in user.keywords"> | ||
<span class="badge badge--greenlight">[[ keyword ]]</span>[[ ' ' ]] | ||
</template> | ||
</div> | ||
<div class=""> | ||
<a :href="`${ user.data.html_url }?tab=repositories`" target="_blank" rel="noopener noreferrer" > | ||
<i class="fab fa-github"></i> | ||
</a> | ||
<a :href="`${ user.data.blog }`" target="_blank" rel="noopener noreferrer" v-if="user.data.blog"> | ||
<i class="fas fa-home"></i> | ||
</a> | ||
<a :href="`mailto:${ user.data.email }`" v-if="user.data.email"> | ||
<i class="far fa-envelope"></i> | ||
</a> | ||
</div> | ||
<div class="my-2 font-smaller-3" id="job_status"> | ||
<a :href="`${media_url}${user.resume}`" download data-toggle="tooltip" title="Download resume" v-if="user.show_job_status"> | ||
<i class="fa fa-briefcase mr-2" aria-hidden="true"></i> | ||
</a> | ||
</div> | ||
<small> | ||
Joined: <time :datetime="[[ user.created_on ]]" :title="[[ user.created_on ]]">[[ user.created_on | moment ]]</time> | ||
</small> | ||
</div> | ||
<!-- [[user]] --> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
|
||
|
||
{% include 'shared/analytics.html' %} | ||
{% include 'shared/footer_scripts.html' %} | ||
{% include 'shared/footer.html' %} | ||
{% include 'shared/messages.html' %} | ||
<script src="{% static "v2/js/popper.min.js" %}"></script> | ||
<script src="{% static "v2/js/bootstrap.min.js" %}"></script> | ||
<script> | ||
let bootstrapTooltip = $.fn.tooltip.noConflict() | ||
$.fn.runTooltip = bootstrapTooltip; | ||
$('[data-toggle="tooltip"]').runTooltip(); | ||
|
||
</script> | ||
<script src="{% static "v2/js/users.js" %}"></script> | ||
|
||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# -*- coding: utf-8 -*- | ||
"""Handle dashboard embed related tests. | ||
|
||
Copyright (C) 2018 Gitcoin Core | ||
|
||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU Affero General Public License as published | ||
by the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
|
||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU Affero General Public License for more details. | ||
|
||
You should have received a copy of the GNU Affero General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
""" | ||
|
||
import json | ||
|
||
from django.contrib.auth.models import User | ||
from django.test.client import RequestFactory | ||
|
||
from dashboard.models import Profile | ||
from dashboard.views import users_fetch | ||
from test_plus.test import TestCase | ||
|
||
|
||
class UsersListTest(TestCase): | ||
"""Define tests for the user list.""" | ||
|
||
def setUp(self): | ||
self.request = RequestFactory() | ||
for i in range(20): | ||
user = User.objects.create(password="{}".format(i), | ||
username="user{}".format(i)) | ||
profile = Profile.objects.create(user=user, data={}, handle="{}".format(i)) | ||
|
||
def test_user_list(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice! :D |
||
assert json.loads(users_fetch(self.request.get('/api/v0.1/users_fetch/')).content)['count'] == 20 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -731,6 +731,50 @@ def onboard(request, flow): | |
return TemplateResponse(request, 'ftux/onboard.html', params) | ||
|
||
|
||
def users_directory(request): | ||
"""Handle displaying users directory page.""" | ||
|
||
params = { | ||
'active': 'users', | ||
'title': 'Users', | ||
'meta_title': "", | ||
'meta_description': "" | ||
} | ||
return TemplateResponse(request, 'dashboard/users.html', params) | ||
|
||
|
||
@require_GET | ||
def users_fetch(request): | ||
"""Handle displaying users.""" | ||
q = request.GET.get('search','') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a space before the final string here like |
||
limit = int(request.GET.get('limit', 10)) | ||
page = int(request.GET.get('page', 1)) | ||
order_by = request.GET.get('order_by', '-created_on') | ||
context = {} | ||
user_list = Profile.objects.all().order_by(order_by).filter(handle__icontains=q).cache() | ||
|
||
# all_notifs = Notification.objects.filter(to_user_id=request.user.id).order_by('-id') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. leftover comment |
||
params = dict() | ||
all_pages = Paginator(user_list, limit) | ||
all_users = [] | ||
for user in all_pages.page(page): | ||
print(user) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. leftover debugging print? |
||
profile_json = {} | ||
profile_json = user.to_standard_dict() | ||
if user.avatar_baseavatar_related.exists(): | ||
profile_json['avatar_id'] = user.avatar_baseavatar_related.first().pk | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better here to assign a var to |
||
profile_json['avatar_url'] = user.avatar_baseavatar_related.first().avatar_url | ||
|
||
profile_json['verification'] = user.get_my_verified_check | ||
all_users.append(profile_json) | ||
# dumping and loading the json here quickly passes serialization issues - definitely can be a better solution | ||
params['data'] = json.loads(json.dumps(all_users, default=str)) | ||
params['has_next'] = all_pages.page(page).has_next() | ||
params['count'] = all_pages.count | ||
params['num_pages'] = all_pages.num_pages | ||
return JsonResponse(params, status=200, safe=False) | ||
|
||
|
||
def dashboard(request): | ||
"""Handle displaying the dashboard.""" | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats up with this fat comment? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is for the next version