Commit fc10c10f authored by Robin Tissot's avatar Robin Tissot
Browse files

Merge branch 'feature/profile-api-token' into 'develop'

Profile page with api token and list of exported files.

See merge request !44
parents e75ffbcd 81e27944
from django.urls import include, path
from rest_framework_nested import routers
from rest_framework.authtoken import views
from api.views import (DocumentViewSet,
UserViewSet,
......@@ -29,5 +30,6 @@ urlpatterns = [
path('', include(router.urls)),
path('', include(documents_router.urls)),
path('', include(parts_router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('token-auth/', views.obtain_auth_token)
]
......@@ -32,7 +32,7 @@ class InvitationAcceptForm(BootstrapFormMixin, UserCreationForm):
"""
This is a registration form since a user is created.
"""
class Meta(UserCreationForm.Meta):
model = User
fields = ('email',
......@@ -41,8 +41,14 @@ class InvitationAcceptForm(BootstrapFormMixin, UserCreationForm):
'last_name',
'password1',
'password2')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].help_text = None
self.fields['password1'].help_text = None
class ProfileForm(BootstrapFormMixin, forms.ModelForm):
class Meta:
model = User
fields = ('email', 'first_name', 'last_name')
from django.urls import path, include
from users.views import SendInvitation, AcceptInvitation
from users.views import SendInvitation, AcceptInvitation, Profile
from django.contrib.auth.decorators import permission_required
urlpatterns = [
path('', include('django.contrib.auth.urls')),
path('profile/', Profile.as_view(), name='user_profile'),
path('invite/',
permission_required('users.can_invite', raise_exception=True)(SendInvitation.as_view()),
name='send-invitation'),
......
from os import listdir, stat
from os.path import isfile, join, relpath
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.core.paginator import Paginator
from django.http import Http404
from django.utils.functional import cached_property
from django.utils.translation import gettext as _
from django.views.generic.edit import CreateView
from django.views.generic.edit import CreateView, UpdateView
from rest_framework.authtoken.models import Token
from users.models import User, Invitation
from users.forms import InvitationForm, InvitationAcceptForm
from users.forms import InvitationForm, InvitationAcceptForm, ProfileForm
class SendInvitation(LoginRequiredMixin, SuccessMessageMixin, CreateView):
......@@ -68,3 +74,32 @@ class AcceptInvitation(CreateView):
return response
# request invitation
class Profile(SuccessMessageMixin, UpdateView):
model = User
form_class = ProfileForm
success_url = '/profile/'
success_message = _('Profile successfully saved.')
template_name = 'users/profile.html'
def get_object(self):
return self.request.user
def get_context_data(self):
context = super().get_context_data()
context['api_auth_token'], created = Token.objects.get_or_create(user=self.object)
# files directory
upath = self.object.get_document_store_path() + '/'
files = listdir(upath)
files.sort(key=lambda x: stat(join(upath, x)).st_mtime)
files.reverse()
files = [(relpath(join(upath, f), settings.MEDIA_ROOT), f)
for f in files
if isfile(join(upath, f))]
paginator = Paginator(files, 25) # Show 25 files per page.
page_number = self.request.GET.get('page')
context['is_paginated'] = paginator.count != 0
context['page_obj'] = paginator.get_page(page_number)
return context
......@@ -57,6 +57,7 @@ INSTALLED_APPS = [
'easy_thumbnails.optimize',
'channels',
'rest_framework',
'rest_framework.authtoken',
'compressor',
'bootstrap',
......@@ -314,6 +315,11 @@ REST_FRAMEWORK = {
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
'rest_framework.permissions.IsAuthenticated'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.BasicAuthentication', # Only for testing
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PAGINATION_CLASS': 'core.pagination.CustomPagination',
'PAGE_SIZE': 10,
}
......
......@@ -74,7 +74,7 @@
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{% url 'password_change' %}">{% trans "Change password" %}</a>
<a class="dropdown-item" href="#">{% trans "Profile" %}</a>
<a class="dropdown-item" href="{% url 'user_profile' %}">{% trans "Profile" %}</a>
<div class="dropdown-divider"></div>
{% if perms.users.can_invite %}<a class="dropdown-item" href="{% url 'send-invitation' %}">Invite</a>{% endif %}
<a class="dropdown-item" href="{% url 'logout' %}">{% trans 'Logout' %}</a>
......
......@@ -12,7 +12,7 @@
<a class="page-link" title="{% trans 'First page' %}" href="?page=1">1</a>
</li>
{% endif %}
<li class="page-item active">
<a class="page-link" href="#">{{page_obj.number}} <span class="sr-only">(current)</span></a>
</li>
......@@ -22,7 +22,7 @@
<a class="page-link" title="{% trans 'Last page' %}" href="?page={{page_obj.paginator.num_pages}}">{{page_obj.paginator.num_pages}}</a>
</li>
{% endif %}
<li class="page-item {% if not page_obj.has_next %}disabled{% endif %}">
<a class="page-link" href="{% if page_obj.has_next %}?page={{page_obj.next_page_number}}{% else %}#{% endif %}"><span aria-hidden="true">&raquo;</span> <span class="sr-only">{% trans "Next" %}</span></a>
</li>
......
{% extends "base.html" %}
{% load i18n bootstrap static %}
{% block body %}
<div class="container">
<div class="row">
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<a class="nav-link active" id="nav-infos-tab" data-toggle="pill" href="#infos-tab" role="tab">{% trans "Informations" %}</a>
<a class="nav-link" id="nav-key-tab" data-toggle="pill" href="#key-tab" role="tab">{% trans "Api key" %}</a>
<a class="nav-link" id="nav-files-tab" data-toggle="pill" href="#files-tab" role="tab">{% trans "Files" %}</a>
</div>
<div class="col-md-8 tab-content" id="v-pills-tabContent">
<div class="tab-pane fade show active" id="infos-tab" role="tabpanel" aria-labelledby="v-pills-home-tab">
<form method="post">
{% csrf_token %}
<fieldset>
{% render_field form.email %}
{% render_field form.first_name %}
{% render_field form.last_name %}
<input type="submit" value="{% trans 'Save' %}" class="btn btn-lg btn-success btn-block">
</fieldset>
</form>
</div>
<div class="tab-pane fade show" id="key-tab" role="tabpanel" aria-labelledby="v-pills-home-tab">
{% trans "API Authentication Token:" %} {{ api_auth_token.key }}
<button class="btn btn-secondary btn-sm fas fa-clipboard float-right" id="api-key-clipboard" title="{% trans "Copy to clipboard." %}" data-key="{{ api_auth_token.key }}"></button>
<br/><span class="text-muted"><small>{% trans "example: "%} $ curl {{request.scheme}}://{{request.get_host}}/api/documents/ -H 'Authorization: Token {{ api_auth_token.key }}'</small></span>
</div>
<div class="tab-pane fade show" id="files-tab" role="tabpanel" aria-labelledby="v-pills-home-tab">
{% for fpath, fname in page_obj %}
<br><a href="{{request.scheme}}://{{request.get_host}}{% get_media_prefix %}{{fpath}}"><i class="fas fa-download"></i></a> {{ fname }}
{% empty %}
{% trans "You don't have any saved files yet." %}
{% endfor %}
{% include "includes/pagination.html" %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ block.super }}
<script>
$(document).ready(function() {
$('#api-key-clipboard').click(function() {
navigator.clipboard.writeText($(this).data('key'))
});
function updatePagination(hash) {
$('ul.pagination a').each(function(i, a) {
let href = $(a).attr("href").split('#')[0];
$(a).attr("href", href+location.hash);
});
}
if (location.hash) {
let tab = location.hash.split('#')[1];
$('#nav-' + tab).tab("show");
updatePagination(location.hash);
}
$('a[data-toggle="pill"]').on("click", function() {
let url = location.href;
let hash = $(this).attr("href");
let newUrl = url.split("#")[0] + hash;
history.replaceState(null, null, newUrl);
updatePagination(hash);
});
}, false);
</script>
{% endblock %}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment