mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-17 15:53:28 +00:00
[api] Add guest invite feature
This commit is contained in:
51
seahub/api2/endpoints/invitation.py
Normal file
51
seahub/api2/endpoints/invitation.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.permissions import CanInviteGuest
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.invitations.models import Invitation
|
||||
|
||||
json_content_type = 'application/json; charset=utf-8'
|
||||
|
||||
def invitation_owner_check(func):
|
||||
"""Check whether user is the invitation inviter.
|
||||
"""
|
||||
def _decorated(view, request, token, *args, **kwargs):
|
||||
i = get_object_or_404(Invitation, token=token)
|
||||
if i.inviter != request.user.username:
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||
|
||||
return func(view, request, i, *args, **kwargs)
|
||||
|
||||
return _decorated
|
||||
|
||||
class InvitationView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated, CanInviteGuest)
|
||||
throttle_classes = (UserRateThrottle, )
|
||||
|
||||
@invitation_owner_check
|
||||
def get(self, request, invitation, format=None):
|
||||
# Get a certain invitation.
|
||||
return Response(invitation.to_dict())
|
||||
|
||||
# @invitation_owner_check
|
||||
# def put(self, request, invitation, format=None):
|
||||
# # Update an invitation.
|
||||
# # TODO
|
||||
# return Response({
|
||||
# }, status=200)
|
||||
|
||||
@invitation_owner_check
|
||||
def delete(self, request, invitation, format=None):
|
||||
# Delete an invitation.
|
||||
invitation.delete()
|
||||
|
||||
return Response({
|
||||
}, status=204)
|
66
seahub/api2/endpoints/invitations.py
Normal file
66
seahub/api2/endpoints/invitations.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.permissions import CanInviteGuest
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.base.accounts import User
|
||||
from seahub.invitations.models import Invitation
|
||||
from seahub.utils import is_valid_email
|
||||
|
||||
json_content_type = 'application/json; charset=utf-8'
|
||||
|
||||
class InvitationsView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated, CanInviteGuest)
|
||||
throttle_classes = (UserRateThrottle, )
|
||||
|
||||
def get(self, request, format=None):
|
||||
# List invitations sent by user.
|
||||
username = request.user.username
|
||||
|
||||
invitations = []
|
||||
for e in Invitation.objects.get_by_inviter(username):
|
||||
invitations.append(e.to_dict())
|
||||
|
||||
return Response({
|
||||
"invitations": invitations
|
||||
})
|
||||
|
||||
def post(self, request, format=None):
|
||||
# Send a invitation.
|
||||
itype = request.data.get('type', '').lower()
|
||||
if not itype or itype != 'guest':
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'type invalid.')
|
||||
|
||||
accepter = request.data.get('accepter', '').lower()
|
||||
if not accepter:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'accepter invalid.')
|
||||
|
||||
if not is_valid_email(accepter):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST,
|
||||
_('Email %s invalid.') % accepter)
|
||||
|
||||
try:
|
||||
User.objects.get(accepter)
|
||||
user_exists = True
|
||||
except User.DoesNotExist:
|
||||
user_exists = False
|
||||
|
||||
if user_exists:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST,
|
||||
_('User %s already exists.') % accepter)
|
||||
|
||||
i = Invitation.objects.add(inviter=request.user.username,
|
||||
accepter=accepter)
|
||||
i.send_to(email=accepter)
|
||||
|
||||
return Response({
|
||||
"accepter_exists": user_exists,
|
||||
"invitation": i.to_dict()
|
||||
}, status=201)
|
@@ -53,3 +53,10 @@ class IsGroupMember(BasePermission):
|
||||
group_id = int(view.kwargs.get('group_id', ''))
|
||||
username = request.user.username if request.user else ''
|
||||
return True if ccnet_api.is_group_user(group_id, username) else False
|
||||
|
||||
|
||||
class CanInviteGuest(BasePermission):
|
||||
"""Check user has permission to invite a guest.
|
||||
"""
|
||||
def has_permission(self, request, *args, **kwargs):
|
||||
return request.user.permissions.can_invite_guest()
|
||||
|
0
seahub/invitations/__init__.py
Normal file
0
seahub/invitations/__init__.py
Normal file
3
seahub/invitations/admin.py
Normal file
3
seahub/invitations/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
25
seahub/invitations/migrations/0001_initial.py
Normal file
25
seahub/invitations/migrations/0001_initial.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import seahub.base.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Invitation',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('token', models.CharField(max_length=40)),
|
||||
('inviter', seahub.base.fields.LowerCaseCharField(max_length=255, db_index=True)),
|
||||
('acceptor', seahub.base.fields.LowerCaseCharField(max_length=255)),
|
||||
('invite_time', models.DateTimeField(auto_now_add=True)),
|
||||
('accept_time', models.DateTimeField(null=True, blank=True)),
|
||||
],
|
||||
),
|
||||
]
|
19
seahub/invitations/migrations/0002_invitation_invite_type.py
Normal file
19
seahub/invitations/migrations/0002_invitation_invite_type.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invitations', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='invitation',
|
||||
name='invite_type',
|
||||
field=models.CharField(default=b'guest', max_length=20, choices=[(b'guest', b'guest')]),
|
||||
),
|
||||
]
|
24
seahub/invitations/migrations/0003_auto_20160510_1703.py
Normal file
24
seahub/invitations/migrations/0003_auto_20160510_1703.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invitations', '0002_invitation_invite_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='invitation',
|
||||
old_name='acceptor',
|
||||
new_name='accepter',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='invitation',
|
||||
name='token',
|
||||
field=models.CharField(max_length=40, db_index=True),
|
||||
),
|
||||
]
|
26
seahub/invitations/migrations/0004_auto_20160629_1610.py
Normal file
26
seahub/invitations/migrations/0004_auto_20160629_1610.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import datetime
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invitations', '0003_auto_20160510_1703'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='invitation',
|
||||
name='expire_date',
|
||||
field=models.DateTimeField(default=datetime.datetime(2016, 6, 29, 16, 10, 45, 816971)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='invitation',
|
||||
name='invite_type',
|
||||
field=models.CharField(default='Guest', max_length=20, choices=[('Guest', 'Guest')]),
|
||||
),
|
||||
]
|
19
seahub/invitations/migrations/0005_auto_20160629_1614.py
Normal file
19
seahub/invitations/migrations/0005_auto_20160629_1614.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invitations', '0004_auto_20160629_1610'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='invitation',
|
||||
old_name='expire_date',
|
||||
new_name='expire_time',
|
||||
),
|
||||
]
|
0
seahub/invitations/migrations/__init__.py
Normal file
0
seahub/invitations/migrations/__init__.py
Normal file
94
seahub/invitations/models.py
Normal file
94
seahub/invitations/models.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from seahub.base.fields import LowerCaseCharField
|
||||
from seahub.invitations.settings import INVITATIONS_TOKEN_AGE
|
||||
from seahub.utils import gen_token
|
||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||
from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
|
||||
from seahub.settings import SITE_NAME
|
||||
|
||||
GUEST = _('Guest')
|
||||
|
||||
class InvitationManager(models.Manager):
|
||||
def add(self, inviter, accepter, invite_type=GUEST):
|
||||
token = gen_token(max_length=32)
|
||||
expire_at = timezone.now() + timedelta(hours=INVITATIONS_TOKEN_AGE)
|
||||
|
||||
i = self.model(token=token, inviter=inviter, accepter=accepter,
|
||||
invite_type=invite_type, expire_time=expire_at)
|
||||
i.save(using=self._db)
|
||||
return i
|
||||
|
||||
def get_by_inviter(self, inviter):
|
||||
return super(InvitationManager, self).filter(inviter=inviter)
|
||||
|
||||
class Invitation(models.Model):
|
||||
INVITE_TYPE_CHOICES = (
|
||||
(GUEST, _('Guest')),
|
||||
)
|
||||
|
||||
token = models.CharField(max_length=40, db_index=True)
|
||||
inviter = LowerCaseCharField(max_length=255, db_index=True)
|
||||
accepter = LowerCaseCharField(max_length=255)
|
||||
invite_type = models.CharField(max_length=20,
|
||||
choices=INVITE_TYPE_CHOICES,
|
||||
default=GUEST)
|
||||
invite_time = models.DateTimeField(auto_now_add=True)
|
||||
accept_time = models.DateTimeField(null=True, blank=True)
|
||||
expire_time = models.DateTimeField()
|
||||
objects = InvitationManager()
|
||||
|
||||
def __unicode__(self):
|
||||
return "Invitation from %s on %s (%s)" % (
|
||||
self.inviter, self.invite_time, self.token)
|
||||
|
||||
def accept(self):
|
||||
self.accept_time = timezone.now()
|
||||
self.save()
|
||||
|
||||
def to_dict(self):
|
||||
accept_time = datetime_to_isoformat_timestr(self.accept_time) \
|
||||
if self.accept_time else ""
|
||||
return {
|
||||
"id": self.pk,
|
||||
"token": self.token,
|
||||
"inviter": self.inviter,
|
||||
"accepter": self.accepter,
|
||||
"type": self.invite_type,
|
||||
"invite_time": datetime_to_isoformat_timestr(self.invite_time),
|
||||
"accept_time": accept_time,
|
||||
"expire_time": datetime_to_isoformat_timestr(self.expire_time),
|
||||
}
|
||||
|
||||
def is_guest(self):
|
||||
return self.invite_type == GUEST
|
||||
|
||||
def is_expired(self):
|
||||
return timezone.now() >= self.expire_time
|
||||
|
||||
def send_to(self, email=None):
|
||||
"""
|
||||
Send an invitation email to ``email``.
|
||||
"""
|
||||
if not email:
|
||||
email = self.accepter
|
||||
|
||||
context = {
|
||||
'inviter': self.inviter,
|
||||
'site_name': SITE_NAME,
|
||||
'token': self.token,
|
||||
}
|
||||
subject = render_to_string('invitations/invitation_email_subject.txt',
|
||||
context)
|
||||
|
||||
send_html_email_with_dj_template(
|
||||
email, dj_template='invitations/invitation_email.html',
|
||||
context=context,
|
||||
subject=subject,
|
||||
priority=MAIL_PRIORITY.now
|
||||
)
|
3
seahub/invitations/settings.py
Normal file
3
seahub/invitations/settings.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.conf import settings
|
||||
|
||||
INVITATIONS_TOKEN_AGE = getattr(settings, 'INVITATIONS_TOKEN_AGE', 72) # hours
|
@@ -0,0 +1,19 @@
|
||||
{% extends 'email_base.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block email_con %}
|
||||
|
||||
{% autoescape off %}
|
||||
|
||||
<p style="color:#121214;font-size:14px;">{% trans "Hi," %}</p>
|
||||
|
||||
<p style="font-size:14px;color:#434144;">
|
||||
{% blocktrans %}{{ inviter }} invites you to join {{ site_name }}. Please click the link below:{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<a href="{{ url_base }}{% url 'invitations:token_view' token %}" target="_blank">{{ url_base }}{% url 'invitations:token_view' token %}</a>
|
||||
|
||||
{% endautoescape %}
|
||||
|
||||
{% endblock %}
|
@@ -0,0 +1 @@
|
||||
{% load i18n%}{% blocktrans %}{{ inviter }} invite you to join {{ site_name}}{% endblocktrans %}
|
31
seahub/invitations/templates/invitations/token_view.html
Normal file
31
seahub/invitations/templates/invitations/token_view.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block sub_title %}{% trans "Create Account" %}{% endblock %}
|
||||
|
||||
{% block extra_style %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/bootstrap.popover.min.css" />
|
||||
{% endblock %}
|
||||
|
||||
{% block main_panel %}
|
||||
<div class="new-narrow-panel">
|
||||
<h2 class="hd">{% trans "Set your password" %}</h2>
|
||||
<form action="" method="post" class="con">{% csrf_token %}
|
||||
<label for="id_email">{% trans "Email address" %} {{ iv.accepter }}</label><br/>
|
||||
|
||||
<label for="id_new_password1">{% trans "Password" %}</label>
|
||||
<input type="password" name="password" id="password" value="" class="input" autocomplete="off" />
|
||||
|
||||
<p class="error hide"></p>
|
||||
<input type="submit" value="{% trans "Submit" %}" class="submit" />
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript" src="{{MEDIA_URL}}js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$('[type="password"]').addClass('input');
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
8
seahub/invitations/urls.py
Normal file
8
seahub/invitations/urls.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .views import token_view
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^token/(?P<token>[a-f0-9]{32})/$', token_view, name='token_view')
|
||||
)
|
45
seahub/invitations/views.py
Normal file
45
seahub/invitations/views.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect, Http404
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from seahub.auth import login as auth_login
|
||||
from seahub.auth import get_backends
|
||||
from seahub.base.accounts import User
|
||||
from seahub.constants import GUEST_USER
|
||||
from seahub.invitations.models import Invitation
|
||||
from seahub.settings import SITE_ROOT
|
||||
|
||||
def token_view(request, token):
|
||||
"""Show form to let user set password.
|
||||
"""
|
||||
i = get_object_or_404(Invitation, token=token)
|
||||
if i.is_expired():
|
||||
raise Http404
|
||||
|
||||
if request.method == 'POST':
|
||||
passwd = request.POST.get('password', '')
|
||||
if not passwd:
|
||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||
|
||||
try:
|
||||
User.objects.get(email=i.accepter)
|
||||
messages.error(request, _('A user with this email already exists.'))
|
||||
except User.DoesNotExist:
|
||||
# Create user, set that user as guest, and log user in.
|
||||
u = User.objects.create_user(email=i.accepter, password=passwd,
|
||||
is_active=True)
|
||||
u.role = GUEST_USER
|
||||
u.save()
|
||||
|
||||
i.accept() # Update invitaion accept time.
|
||||
|
||||
for backend in get_backends():
|
||||
u.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
|
||||
auth_login(request, u)
|
||||
return HttpResponseRedirect(SITE_ROOT)
|
||||
|
||||
return render_to_response('invitations/token_view.html', {
|
||||
'iv': i,
|
||||
}, context_instance=RequestContext(request))
|
@@ -200,6 +200,7 @@ INSTALLED_APPS = (
|
||||
'seahub.base',
|
||||
'seahub.contacts',
|
||||
'seahub.institutions',
|
||||
'seahub.invitations',
|
||||
'seahub.wiki',
|
||||
'seahub.group',
|
||||
'seahub.message',
|
||||
|
@@ -62,6 +62,10 @@
|
||||
<a href="{{ SITE_ROOT }}sys/virus_scan_records/"><span class="sf2-icon-security"></span>{% trans "Virus Scan" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="tab {% block cur_invitations %}{% endblock %}">
|
||||
<a href="{{ SITE_ROOT }}sys/invitationadmin/"><span class="sf2-icon-security"></span>{% trans "Invitations" %}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
42
seahub/templates/sysadmin/sys_invitations_admin.html
Normal file
42
seahub/templates/sysadmin/sys_invitations_admin.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% extends "sysadmin/base.html" %}
|
||||
{% load i18n seahub_tags %}
|
||||
|
||||
{% block cur_invitations %}tab-cur{% endblock %}
|
||||
|
||||
{% block right_panel %}
|
||||
<h3 class="hd">{% trans "All Invitations" %}</h3>
|
||||
|
||||
{% if invitations %}
|
||||
<table>
|
||||
<tr>
|
||||
<th width="25%">{% trans "Inviter" %}</th>
|
||||
<th width="25%">{% trans "Accepter" %}</th>
|
||||
<th width="10%">{% trans "Type" %}</th>
|
||||
<th width="20%">{% trans "Invited at" %}</th>
|
||||
<th width="20%">{% trans "Accepted at" %}</th>
|
||||
</tr>
|
||||
{% for invitation in invitations %}
|
||||
<tr>
|
||||
<td><a href="{% url 'user_info' invitation.inviter %}">{{ invitation.inviter }}</a></td>
|
||||
<td><a href="{% url 'user_info' invitation.accepter %}">{{ invitation.accepter }}</a></td>
|
||||
<td>{{ invitation.invite_type }}</td>
|
||||
<td>{{ invitation.invite_time|translate_seahub_time }} </td>
|
||||
{% if invitation.accept_time %}
|
||||
<td>{{ invitation.accept_time|translate_seahub_time }}</td>
|
||||
{% else %}
|
||||
<td>--</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% include "snippets/admin_paginator.html" %}
|
||||
{% else %}
|
||||
<p>{% trans "Empty" %}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
{% endblock %}
|
@@ -41,6 +41,8 @@ from seahub.api2.endpoints.admin.libraries import AdminLibraries, AdminLibrary
|
||||
from seahub.api2.endpoints.admin.library_dirents import AdminLibraryDirents, AdminLibraryDirent
|
||||
from seahub.api2.endpoints.admin.system_library import AdminSystemLibrary
|
||||
from seahub.api2.endpoints.admin.trash_libraries import AdminTrashLibraries, AdminTrashLibrary
|
||||
from seahub.api2.endpoints.invitations import InvitationsView
|
||||
from seahub.api2.endpoints.invitation import InvitationView
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
#from django.contrib import admin
|
||||
@@ -198,6 +200,8 @@ urlpatterns = patterns(
|
||||
url(r'^api/v2.1/admin/sysinfo/$', SysInfo.as_view(), name='api-v2.1-sysinfo'),
|
||||
url(r'^api/v2.1/admin/devices/$', AdminDevices.as_view(), name='api-v2.1-admin-devices'),
|
||||
url(r'^api/v2.1/admin/device-errors/$', AdminDeviceErrors.as_view(), name='api-v2.1-admin-device-errors'),
|
||||
url(r'^api/v2.1/invitations/$', InvitationsView.as_view()),
|
||||
url(r'^api/v2.1/invitations/(?P<token>[a-f0-9]{32})/$', InvitationView.as_view()),
|
||||
|
||||
url(r'^api/v2.1/admin/libraries/$', AdminLibraries.as_view(), name='api-v2.1-admin-libraries'),
|
||||
url(r'^api/v2.1/admin/libraries/(?P<repo_id>[-0-9a-f]{36})/$', AdminLibrary.as_view(), name='api-v2.1-admin-library'),
|
||||
@@ -219,6 +223,7 @@ urlpatterns = patterns(
|
||||
url(r'^captcha/', include('captcha.urls')),
|
||||
(r'^thumbnail/', include('seahub.thumbnail.urls')),
|
||||
url(r'^inst/', include('seahub.institutions.urls', app_name='institutions', namespace='institutions')),
|
||||
url(r'^invite/', include('seahub.invitations.urls', app_name='invitations', namespace='invitations')),
|
||||
|
||||
### system admin ###
|
||||
url(r'^sysadmin/$', sysadmin, name='sysadmin'),
|
||||
@@ -260,6 +265,7 @@ urlpatterns = patterns(
|
||||
url(r'^sys/publink/remove/$', sys_publink_remove, name='sys_publink_remove'),
|
||||
url(r'^sys/uploadlink/remove/$', sys_upload_link_remove, name='sys_upload_link_remove'),
|
||||
url(r'^sys/notificationadmin/', notification_list, name='notification_list'),
|
||||
url(r'^sys/invitationadmin/$', sys_invitation_admin, name='sys_invitaion_admin'),
|
||||
url(r'^sys/sudo/', sys_sudo_mode, name='sys_sudo_mode'),
|
||||
url(r'^sys/check-license/', sys_check_license, name='sys_check_license'),
|
||||
url(r'^useradmin/add/$', user_add, name="user_add"),
|
||||
|
@@ -34,6 +34,7 @@ from seahub.auth import authenticate
|
||||
from seahub.auth.decorators import login_required, login_required_ajax
|
||||
from seahub.constants import GUEST_USER, DEFAULT_USER
|
||||
from seahub.institutions.models import Institution, InstitutionAdmin
|
||||
from seahub.invitations.models import Invitation
|
||||
from seahub.role_permissions.utils import get_available_roles
|
||||
from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \
|
||||
is_pro_version, send_html_email, get_user_traffic_list, get_server_id, \
|
||||
@@ -2240,3 +2241,36 @@ def sys_inst_toggle_admin(request, inst_id, email):
|
||||
|
||||
messages.success(request, _('Success'))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
@login_required
|
||||
@sys_staff_required
|
||||
def sys_invitation_admin(request):
|
||||
"""List all invitations .
|
||||
"""
|
||||
# Make sure page request is an int. If not, deliver first page.
|
||||
try:
|
||||
current_page = int(request.GET.get('page', '1'))
|
||||
per_page = int(request.GET.get('per_page', '100'))
|
||||
except ValueError:
|
||||
current_page = 1
|
||||
per_page = 100
|
||||
|
||||
offset = per_page * (current_page - 1)
|
||||
limit = per_page + 1
|
||||
invitations = Invitation.objects.all()[offset:offset + limit]
|
||||
|
||||
if len(invitations) == per_page + 1:
|
||||
page_next = True
|
||||
else:
|
||||
page_next = False
|
||||
|
||||
return render_to_response(
|
||||
'sysadmin/sys_invitations_admin.html', {
|
||||
'invitations': invitations,
|
||||
'current_page': current_page,
|
||||
'prev_page': current_page-1,
|
||||
'next_page': current_page+1,
|
||||
'per_page': per_page,
|
||||
'page_next': page_next,
|
||||
},
|
||||
context_instance=RequestContext(request))
|
||||
|
60
tests/api/endpoints/test_invitation.py
Normal file
60
tests/api/endpoints/test_invitation.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import json
|
||||
from mock import patch
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from seahub.base.accounts import UserPermissions
|
||||
from seahub.invitations.models import Invitation
|
||||
from seahub.test_utils import BaseTestCase
|
||||
|
||||
|
||||
class InvitationsTest(BaseTestCase):
|
||||
def setUp(self):
|
||||
self.login_as(self.user)
|
||||
self.username = self.user.username
|
||||
|
||||
self.i = Invitation.objects.add(inviter=self.username,
|
||||
accepter='1@1.com')
|
||||
self.endpoint = '/api/v2.1/invitations/' + self.i.token + '/'
|
||||
assert len(Invitation.objects.all()) == 1
|
||||
|
||||
@patch.object(UserPermissions, 'can_invite_guest')
|
||||
def test_can_get_one(self, mock_can_invite_guest):
|
||||
mock_can_invite_guest.return_val = True
|
||||
|
||||
resp = self.client.get(self.endpoint)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
json_resp = json.loads(resp.content)
|
||||
assert json_resp['inviter'] == self.username
|
||||
assert json_resp['accepter'] == '1@1.com'
|
||||
|
||||
@patch.object(UserPermissions, 'can_invite_guest')
|
||||
def test_get_invalid(self, mock_can_invite_guest):
|
||||
mock_can_invite_guest.return_val = True
|
||||
|
||||
self.i.delete()
|
||||
assert len(Invitation.objects.all()) == 0
|
||||
|
||||
resp = self.client.get(self.endpoint)
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
||||
@patch.object(UserPermissions, 'can_invite_guest')
|
||||
def test_get_permission_denied(self, mock_can_invite_guest):
|
||||
mock_can_invite_guest.return_val = True
|
||||
|
||||
self.logout()
|
||||
self.login_as(self.admin)
|
||||
|
||||
resp = self.client.get(self.endpoint)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
self.logout()
|
||||
|
||||
@patch.object(UserPermissions, 'can_invite_guest')
|
||||
def test_can_delete(self, mock_can_invite_guest):
|
||||
mock_can_invite_guest.return_val = True
|
||||
|
||||
resp = self.client.delete(self.endpoint)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
|
||||
assert len(Invitation.objects.all()) == 0
|
63
tests/api/endpoints/test_invitations.py
Normal file
63
tests/api/endpoints/test_invitations.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import json
|
||||
from mock import patch
|
||||
|
||||
from post_office.models import Email
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from seahub.base.accounts import UserPermissions
|
||||
from seahub.invitations.models import Invitation
|
||||
from seahub.test_utils import BaseTestCase
|
||||
|
||||
class InvitationsTest(BaseTestCase):
|
||||
def setUp(self):
|
||||
self.login_as(self.user)
|
||||
self.endpoint = '/api/v2.1/invitations/'
|
||||
self.username = self.user.username
|
||||
|
||||
@patch.object(UserPermissions, 'can_invite_guest')
|
||||
def test_can_add(self, mock_can_invite_guest):
|
||||
mock_can_invite_guest.return_val = True
|
||||
|
||||
assert len(Invitation.objects.all()) == 0
|
||||
resp = self.client.post(self.endpoint, {
|
||||
'type': 'guest',
|
||||
'accepter': 'some_random_user@1.com',
|
||||
})
|
||||
self.assertEqual(201, resp.status_code)
|
||||
|
||||
json_resp = json.loads(resp.content)
|
||||
assert json_resp['accepter_exists'] is False
|
||||
assert json_resp['invitation']['inviter'] == self.username
|
||||
assert json_resp['invitation']['accepter'] == 'some_random_user@1.com'
|
||||
assert json_resp['invitation']['expire_time'] is not None
|
||||
|
||||
assert len(Invitation.objects.all()) == 1
|
||||
|
||||
@patch.object(UserPermissions, 'can_invite_guest')
|
||||
def test_can_send_mail(self, mock_can_invite_guest):
|
||||
mock_can_invite_guest.return_val = True
|
||||
|
||||
self.assertEqual(len(Email.objects.all()), 0)
|
||||
|
||||
resp = self.client.post(self.endpoint, {
|
||||
'type': 'guest',
|
||||
'accepter': 'some_random_user@1.com',
|
||||
})
|
||||
self.assertEqual(201, resp.status_code)
|
||||
json_resp = json.loads(resp.content)
|
||||
|
||||
self.assertEqual(len(Email.objects.all()), 1)
|
||||
self.assertRegexpMatches(Email.objects.all()[0].html_message,
|
||||
json_resp['invitation']['token'])
|
||||
|
||||
@patch.object(UserPermissions, 'can_invite_guest')
|
||||
def test_can_list(self, mock_can_invite_guest):
|
||||
mock_can_invite_guest.return_val = True
|
||||
|
||||
Invitation.objects.add(inviter=self.username, accepter='1@1.com')
|
||||
Invitation.objects.add(inviter=self.username, accepter='1@2.com')
|
||||
|
||||
resp = self.client.get(self.endpoint)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
json_resp = json.loads(resp.content)
|
||||
assert len(json_resp['invitations']) == 2
|
0
tests/seahub/invitations/__init__.py
Normal file
0
tests/seahub/invitations/__init__.py
Normal file
26
tests/seahub/invitations/test_models.py
Normal file
26
tests/seahub/invitations/test_models.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.utils import timezone
|
||||
from django.test import override_settings
|
||||
|
||||
from seahub.invitations.models import Invitation
|
||||
from seahub.test_utils import BaseTestCase
|
||||
|
||||
|
||||
class InvitationTest(BaseTestCase):
|
||||
def test_is_expired(self):
|
||||
i = Invitation.objects.add('f@f.com', 'g@g.com')
|
||||
assert i.is_expired() is False
|
||||
|
||||
i.expire_time = timezone.now()
|
||||
i.save()
|
||||
assert i.is_expired() is True
|
||||
|
||||
|
||||
class InvitationManagerTest(BaseTestCase):
|
||||
def test_can_add(self):
|
||||
assert len(Invitation.objects.all()) == 0
|
||||
|
||||
i = Invitation.objects.add('f@f.com', 'g@g.com')
|
||||
assert i is not None
|
||||
assert i.is_expired() is False
|
||||
|
||||
assert len(Invitation.objects.all()) == 1
|
43
tests/seahub/invitations/test_views.py
Normal file
43
tests/seahub/invitations/test_views.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from django.utils import timezone
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from seahub.invitations.models import Invitation
|
||||
from seahub.test_utils import BaseTestCase
|
||||
|
||||
|
||||
class TokenViewTest(BaseTestCase):
|
||||
def setUp(self):
|
||||
self.accepter = 'random@foo.com'
|
||||
self.iv = Invitation.objects.add(inviter=self.user.username,
|
||||
accepter=self.accepter)
|
||||
self.url = reverse('invitations:token_view', args=[self.iv.token])
|
||||
|
||||
def tearDown(self):
|
||||
self.remove_user(self.accepter)
|
||||
|
||||
def test_get(self):
|
||||
resp = self.client.get(self.url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertRegexpMatches(resp.content, 'Set your password')
|
||||
|
||||
def test_expired_token(self):
|
||||
self.iv.expire_time = timezone.now()
|
||||
self.iv.save()
|
||||
resp = self.client.get(self.url)
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
||||
def test_post(self):
|
||||
assert self.iv.accept_time is None
|
||||
resp = self.client.post(self.url, {
|
||||
'password': 'passwd'
|
||||
})
|
||||
self.assertEqual(302, resp.status_code)
|
||||
assert Invitation.objects.get(pk=self.iv.pk).accept_time is not None
|
||||
|
||||
def test_post_empty_password(self):
|
||||
assert self.iv.accept_time is None
|
||||
resp = self.client.post(self.url, {
|
||||
'password': '',
|
||||
})
|
||||
self.assertEqual(302, resp.status_code)
|
||||
assert Invitation.objects.get(pk=self.iv.pk).accept_time is None
|
11
tests/seahub/views/sysadmin/test_sys_invitation_admin.py
Normal file
11
tests/seahub/views/sysadmin/test_sys_invitation_admin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from seahub.test_utils import BaseTestCase
|
||||
|
||||
class SysInvitationAdminTest(BaseTestCase):
|
||||
def test_can_list(self):
|
||||
self.login_as(self.admin)
|
||||
|
||||
resp = self.client.get(reverse('sys_invitaion_admin'))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed('sysadmin/sys_invitations_admin.html')
|
Reference in New Issue
Block a user