1
0
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:
zhengxie
2016-05-13 15:34:49 +08:00
committed by lian
parent f29855a3c1
commit 7452c0cfc2
29 changed files with 731 additions and 0 deletions

View 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)

View 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)

View File

@@ -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()

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View 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)),
],
),
]

View 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')]),
),
]

View 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),
),
]

View 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')]),
),
]

View 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',
),
]

View 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
)

View File

@@ -0,0 +1,3 @@
from django.conf import settings
INVITATIONS_TOKEN_AGE = getattr(settings, 'INVITATIONS_TOKEN_AGE', 72) # hours

View File

@@ -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 %}

View File

@@ -0,0 +1 @@
{% load i18n%}{% blocktrans %}{{ inviter }} invite you to join {{ site_name}}{% endblocktrans %}

View 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 %}

View 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')
)

View 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))

View File

@@ -200,6 +200,7 @@ INSTALLED_APPS = (
'seahub.base',
'seahub.contacts',
'seahub.institutions',
'seahub.invitations',
'seahub.wiki',
'seahub.group',
'seahub.message',

View File

@@ -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>

View 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 %}

View File

@@ -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"),

View File

@@ -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))

View 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

View 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

View File

View 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

View 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

View 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')