mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-07 09:51:26 +00:00
add ENABLE_GUEST_INVITATION setting
This commit is contained in:
@@ -18,7 +18,7 @@ json_content_type = 'application/json; charset=utf-8'
|
|||||||
class InvitationsView(APIView):
|
class InvitationsView(APIView):
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated, CanInviteGuest)
|
permission_classes = (IsAuthenticated, CanInviteGuest)
|
||||||
throttle_classes = (UserRateThrottle, )
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
# List invitations sent by user.
|
# List invitations sent by user.
|
||||||
|
@@ -4,6 +4,8 @@ Provides a set of pluggable permission policies.
|
|||||||
|
|
||||||
from rest_framework.permissions import BasePermission
|
from rest_framework.permissions import BasePermission
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from seaserv import check_permission, is_repo_owner, ccnet_api
|
from seaserv import check_permission, is_repo_owner, ccnet_api
|
||||||
|
|
||||||
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
|
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
|
||||||
@@ -59,4 +61,5 @@ class CanInviteGuest(BasePermission):
|
|||||||
"""Check user has permission to invite a guest.
|
"""Check user has permission to invite a guest.
|
||||||
"""
|
"""
|
||||||
def has_permission(self, request, *args, **kwargs):
|
def has_permission(self, request, *args, **kwargs):
|
||||||
return request.user.permissions.can_invite_guest()
|
return settings.ENABLE_GUEST_INVITATION and \
|
||||||
|
request.user.permissions.can_invite_guest()
|
||||||
|
@@ -14,7 +14,7 @@ from constance import config
|
|||||||
|
|
||||||
from seahub.settings import SEAFILE_VERSION, SITE_TITLE, SITE_NAME, \
|
from seahub.settings import SEAFILE_VERSION, SITE_TITLE, SITE_NAME, \
|
||||||
MAX_FILE_NAME, BRANDING_CSS, LOGO_PATH, LOGO_WIDTH, LOGO_HEIGHT,\
|
MAX_FILE_NAME, BRANDING_CSS, LOGO_PATH, LOGO_WIDTH, LOGO_HEIGHT,\
|
||||||
SHOW_REPO_DOWNLOAD_BUTTON, SITE_ROOT
|
SHOW_REPO_DOWNLOAD_BUTTON, SITE_ROOT, ENABLE_GUEST_INVITATION
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from seahub.settings import SEACLOUD_MODE
|
from seahub.settings import SEACLOUD_MODE
|
||||||
@@ -82,4 +82,5 @@ def base(request):
|
|||||||
'SITE_ROOT': SITE_ROOT,
|
'SITE_ROOT': SITE_ROOT,
|
||||||
'constance_enabled': dj_settings.CONSTANCE_ENABLED,
|
'constance_enabled': dj_settings.CONSTANCE_ENABLED,
|
||||||
'FILE_SERVER_ROOT': file_server_root,
|
'FILE_SERVER_ROOT': file_server_root,
|
||||||
|
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
|
||||||
}
|
}
|
||||||
|
@@ -528,6 +528,8 @@ ENABLED_ROLE_PERMISSIONS = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ENABLE_GUEST_INVITATION = False
|
||||||
|
|
||||||
#####################
|
#####################
|
||||||
# Sudo Mode #
|
# Sudo Mode #
|
||||||
#####################
|
#####################
|
||||||
|
@@ -45,7 +45,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li class="tab"><a href="{{ SITE_ROOT }}#devices/" class="ellipsis" title="{% trans "Linked Devices" %}"><span class="sf2-icon-monitor" aria-hidden="true"></span>{% trans "Linked Devices" %}</a></li>
|
<li class="tab"><a href="{{ SITE_ROOT }}#devices/" class="ellipsis" title="{% trans "Linked Devices" %}"><span class="sf2-icon-monitor" aria-hidden="true"></span>{% trans "Linked Devices" %}</a></li>
|
||||||
{% if user.permissions.can_invite_guest %}
|
{% if enable_guest_invitation and user.permissions.can_invite_guest %}
|
||||||
<li class="tab">
|
<li class="tab">
|
||||||
<a href="{{ SITE_ROOT }}#invitations/"><span aria-hidden="true" class="sf2-icon-invite"></span>{% trans "Invite People" %}</a>
|
<a href="{{ SITE_ROOT }}#invitations/"><span aria-hidden="true" class="sf2-icon-invite"></span>{% trans "Invite People" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -56,9 +56,11 @@
|
|||||||
<a href="{{ SITE_ROOT }}sys/virus_scan_records/"><span class="sf2-icon-security"></span>{% trans "Virus Scan" %}</a>
|
<a href="{{ SITE_ROOT }}sys/virus_scan_records/"><span class="sf2-icon-security"></span>{% trans "Virus Scan" %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if enable_guest_invitation %}
|
||||||
<li class="tab">
|
<li class="tab">
|
||||||
<a href="{{ SITE_ROOT }}sys/invitationadmin/"><span class="sf2-icon-invite"></span>{% trans "Invitations" %}</a>
|
<a href="{{ SITE_ROOT }}sys/invitationadmin/"><span class="sf2-icon-invite"></span>{% trans "Invitations" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<% if (cur_tab == 'libraries') { %>
|
<% if (cur_tab == 'libraries') { %>
|
||||||
<% if (option == 'all') { %>
|
<% if (option == 'all') { %>
|
||||||
|
@@ -694,7 +694,7 @@
|
|||||||
<li class="tab<% if (cur_tab == 'devices') { %> tab-cur<% } %>">
|
<li class="tab<% if (cur_tab == 'devices') { %> tab-cur<% } %>">
|
||||||
<a href="{{ SITE_ROOT }}#devices/" class="ellipsis" title="{% trans "Linked Devices" %}"><span aria-hidden="true" class="sf2-icon-monitor"></span>{% trans "Linked Devices" %}</a>
|
<a href="{{ SITE_ROOT }}#devices/" class="ellipsis" title="{% trans "Linked Devices" %}"><span aria-hidden="true" class="sf2-icon-monitor"></span>{% trans "Linked Devices" %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% if user.permissions.can_invite_guest %}
|
{% if enable_guest_invitation and user.permissions.can_invite_guest %}
|
||||||
<li class="tab<% if (cur_tab == 'invitations') { %> tab-cur<% } %>">
|
<li class="tab<% if (cur_tab == 'invitations') { %> tab-cur<% } %>">
|
||||||
<a href="{{ SITE_ROOT }}#invitations/"><span aria-hidden="true" class="sf2-icon-invite"></span>{% trans "Invite People" %}</a>
|
<a href="{{ SITE_ROOT }}#invitations/"><span aria-hidden="true" class="sf2-icon-invite"></span>{% trans "Invite People" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -62,10 +62,11 @@
|
|||||||
<a href="{{ SITE_ROOT }}sys/virus_scan_records/"><span class="sf2-icon-security"></span>{% trans "Virus Scan" %}</a>
|
<a href="{{ SITE_ROOT }}sys/virus_scan_records/"><span class="sf2-icon-security"></span>{% trans "Virus Scan" %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if enable_guest_invitation %}
|
||||||
<li class="tab {% block cur_invitations %}{% endblock %}">
|
<li class="tab {% block cur_invitations %}{% endblock %}">
|
||||||
<a href="{{ SITE_ROOT }}sys/invitationadmin/"><span class="sf2-icon-invite"></span>{% trans "Invitations" %}</a>
|
<a href="{{ SITE_ROOT }}sys/invitationadmin/"><span class="sf2-icon-invite"></span>{% trans "Invitations" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -6,7 +6,6 @@ import logging
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
import stat
|
|
||||||
import csv, chardet, StringIO
|
import csv, chardet, StringIO
|
||||||
import time
|
import time
|
||||||
from constance import config
|
from constance import config
|
||||||
@@ -38,9 +37,8 @@ from seahub.invitations.models import Invitation
|
|||||||
from seahub.role_permissions.utils import get_available_roles
|
from seahub.role_permissions.utils import get_available_roles
|
||||||
from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \
|
from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \
|
||||||
is_pro_version, send_html_email, get_user_traffic_list, get_server_id, \
|
is_pro_version, send_html_email, get_user_traffic_list, get_server_id, \
|
||||||
clear_token, gen_file_get_url, is_org_context, handle_virus_record, \
|
clear_token, handle_virus_record, get_virus_record_by_id, \
|
||||||
get_virus_record_by_id, get_virus_record, FILE_AUDIT_ENABLED, \
|
get_virus_record, FILE_AUDIT_ENABLED, get_max_upload_file_size
|
||||||
get_max_upload_file_size
|
|
||||||
from seahub.utils.file_size import get_file_size_unit
|
from seahub.utils.file_size import get_file_size_unit
|
||||||
from seahub.utils.rpc import mute_seafile_api
|
from seahub.utils.rpc import mute_seafile_api
|
||||||
from seahub.utils.licenseparse import parse_license
|
from seahub.utils.licenseparse import parse_license
|
||||||
@@ -51,7 +49,7 @@ from seahub.utils.user_permissions import (get_basic_user_roles,
|
|||||||
get_user_role)
|
get_user_role)
|
||||||
from seahub.views.ajax import (get_related_users_by_org_repo,
|
from seahub.views.ajax import (get_related_users_by_org_repo,
|
||||||
get_related_users_by_repo)
|
get_related_users_by_repo)
|
||||||
from seahub.views import get_system_default_repo_id, gen_path_link
|
from seahub.views import get_system_default_repo_id
|
||||||
from seahub.forms import SetUserQuotaForm, AddUserForm, BatchAddUserForm
|
from seahub.forms import SetUserQuotaForm, AddUserForm, BatchAddUserForm
|
||||||
from seahub.options.models import UserOptions
|
from seahub.options.models import UserOptions
|
||||||
from seahub.profile.models import Profile, DetailedProfile
|
from seahub.profile.models import Profile, DetailedProfile
|
||||||
@@ -60,7 +58,7 @@ from seahub.share.models import FileShare, UploadLinkShare
|
|||||||
import seahub.settings as settings
|
import seahub.settings as settings
|
||||||
from seahub.settings import INIT_PASSWD, SITE_NAME, SITE_ROOT, \
|
from seahub.settings import INIT_PASSWD, SITE_NAME, SITE_ROOT, \
|
||||||
SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, SEND_EMAIL_ON_RESETTING_USER_PASSWD, \
|
SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, SEND_EMAIL_ON_RESETTING_USER_PASSWD, \
|
||||||
ENABLE_SYS_ADMIN_VIEW_REPO
|
ENABLE_SYS_ADMIN_VIEW_REPO, ENABLE_GUEST_INVITATION
|
||||||
try:
|
try:
|
||||||
from seahub.settings import ENABLE_TRIAL_ACCOUNT
|
from seahub.settings import ENABLE_TRIAL_ACCOUNT
|
||||||
except:
|
except:
|
||||||
@@ -2247,6 +2245,10 @@ def sys_inst_toggle_admin(request, inst_id, email):
|
|||||||
def sys_invitation_admin(request):
|
def sys_invitation_admin(request):
|
||||||
"""List all invitations .
|
"""List all invitations .
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not ENABLE_GUEST_INVITATION:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
# Make sure page request is an int. If not, deliver first page.
|
# Make sure page request is an int. If not, deliver first page.
|
||||||
try:
|
try:
|
||||||
current_page = int(request.GET.get('page', '1'))
|
current_page = int(request.GET.get('page', '1'))
|
||||||
@@ -2257,7 +2259,7 @@ def sys_invitation_admin(request):
|
|||||||
|
|
||||||
offset = per_page * (current_page - 1)
|
offset = per_page * (current_page - 1)
|
||||||
limit = per_page + 1
|
limit = per_page + 1
|
||||||
invitations = Invitation.objects.all()[offset:offset + limit]
|
invitations = Invitation.objects.all().order_by('-invite_time')[offset:offset + limit]
|
||||||
|
|
||||||
if len(invitations) == per_page + 1:
|
if len(invitations) == per_page + 1:
|
||||||
page_next = True
|
page_next = True
|
||||||
|
@@ -73,7 +73,7 @@ define([
|
|||||||
error: function(collection, response, options) {
|
error: function(collection, response, options) {
|
||||||
var err_msg;
|
var err_msg;
|
||||||
if (response.responseText) {
|
if (response.responseText) {
|
||||||
err_msg = response.responseJSON.error_msg;
|
err_msg = response.responseJSON.error_msg||response.responseJSON.detail;
|
||||||
} else {
|
} else {
|
||||||
err_msg = gettext('Please check the network.');
|
err_msg = gettext('Please check the network.');
|
||||||
}
|
}
|
||||||
|
@@ -349,7 +349,7 @@ define([
|
|||||||
ajaxErrorHandler: function(xhr, textStatus, errorThrown) {
|
ajaxErrorHandler: function(xhr, textStatus, errorThrown) {
|
||||||
if (xhr.responseText) {
|
if (xhr.responseText) {
|
||||||
var parsed_resp = $.parseJSON(xhr.responseText);
|
var parsed_resp = $.parseJSON(xhr.responseText);
|
||||||
this.feedback(parsed_resp.error||parsed_resp.error_msg, 'error');
|
this.feedback(parsed_resp.error||parsed_resp.error_msg||parsed_resp.detail, 'error');
|
||||||
} else {
|
} else {
|
||||||
this.feedback(gettext("Failed. Please check the network."), 'error');
|
this.feedback(gettext("Failed. Please check the network."), 'error');
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
from seahub.base.accounts import UserPermissions
|
from seahub.base.accounts import UserPermissions
|
||||||
from seahub.invitations.models import Invitation
|
from seahub.invitations.models import Invitation
|
||||||
from seahub.test_utils import BaseTestCase
|
from seahub.test_utils import BaseTestCase
|
||||||
|
from seahub.api2.permissions import CanInviteGuest
|
||||||
|
|
||||||
|
|
||||||
class InvitationsTest(BaseTestCase):
|
class InvitationsTest(BaseTestCase):
|
||||||
@@ -18,9 +17,12 @@ class InvitationsTest(BaseTestCase):
|
|||||||
self.endpoint = '/api/v2.1/invitations/' + self.i.token + '/'
|
self.endpoint = '/api/v2.1/invitations/' + self.i.token + '/'
|
||||||
assert len(Invitation.objects.all()) == 1
|
assert len(Invitation.objects.all()) == 1
|
||||||
|
|
||||||
|
@patch.object(CanInviteGuest, 'has_permission')
|
||||||
@patch.object(UserPermissions, 'can_invite_guest')
|
@patch.object(UserPermissions, 'can_invite_guest')
|
||||||
def test_can_get_one(self, mock_can_invite_guest):
|
def test_can_get_one(self, mock_can_invite_guest, mock_has_permission):
|
||||||
|
|
||||||
mock_can_invite_guest.return_val = True
|
mock_can_invite_guest.return_val = True
|
||||||
|
mock_has_permission.return_val = True
|
||||||
|
|
||||||
resp = self.client.get(self.endpoint)
|
resp = self.client.get(self.endpoint)
|
||||||
self.assertEqual(200, resp.status_code)
|
self.assertEqual(200, resp.status_code)
|
||||||
@@ -29,9 +31,12 @@ class InvitationsTest(BaseTestCase):
|
|||||||
assert json_resp['inviter'] == self.username
|
assert json_resp['inviter'] == self.username
|
||||||
assert json_resp['accepter'] == '1@1.com'
|
assert json_resp['accepter'] == '1@1.com'
|
||||||
|
|
||||||
|
@patch.object(CanInviteGuest, 'has_permission')
|
||||||
@patch.object(UserPermissions, 'can_invite_guest')
|
@patch.object(UserPermissions, 'can_invite_guest')
|
||||||
def test_get_invalid(self, mock_can_invite_guest):
|
def test_get_invalid(self, mock_can_invite_guest, mock_has_permission):
|
||||||
|
|
||||||
mock_can_invite_guest.return_val = True
|
mock_can_invite_guest.return_val = True
|
||||||
|
mock_has_permission.return_val = True
|
||||||
|
|
||||||
self.i.delete()
|
self.i.delete()
|
||||||
assert len(Invitation.objects.all()) == 0
|
assert len(Invitation.objects.all()) == 0
|
||||||
@@ -39,9 +44,12 @@ class InvitationsTest(BaseTestCase):
|
|||||||
resp = self.client.get(self.endpoint)
|
resp = self.client.get(self.endpoint)
|
||||||
self.assertEqual(404, resp.status_code)
|
self.assertEqual(404, resp.status_code)
|
||||||
|
|
||||||
|
@patch.object(CanInviteGuest, 'has_permission')
|
||||||
@patch.object(UserPermissions, 'can_invite_guest')
|
@patch.object(UserPermissions, 'can_invite_guest')
|
||||||
def test_get_permission_denied(self, mock_can_invite_guest):
|
def test_get_permission_denied(self, mock_can_invite_guest, mock_has_permission):
|
||||||
|
|
||||||
mock_can_invite_guest.return_val = True
|
mock_can_invite_guest.return_val = True
|
||||||
|
mock_has_permission.return_val = True
|
||||||
|
|
||||||
self.logout()
|
self.logout()
|
||||||
self.login_as(self.admin)
|
self.login_as(self.admin)
|
||||||
@@ -50,9 +58,12 @@ class InvitationsTest(BaseTestCase):
|
|||||||
self.assertEqual(403, resp.status_code)
|
self.assertEqual(403, resp.status_code)
|
||||||
self.logout()
|
self.logout()
|
||||||
|
|
||||||
|
@patch.object(CanInviteGuest, 'has_permission')
|
||||||
@patch.object(UserPermissions, 'can_invite_guest')
|
@patch.object(UserPermissions, 'can_invite_guest')
|
||||||
def test_can_delete(self, mock_can_invite_guest):
|
def test_can_delete(self, mock_can_invite_guest, mock_has_permission):
|
||||||
|
|
||||||
mock_can_invite_guest.return_val = True
|
mock_can_invite_guest.return_val = True
|
||||||
|
mock_has_permission.return_val = True
|
||||||
|
|
||||||
resp = self.client.delete(self.endpoint)
|
resp = self.client.delete(self.endpoint)
|
||||||
self.assertEqual(204, resp.status_code)
|
self.assertEqual(204, resp.status_code)
|
||||||
|
@@ -2,11 +2,11 @@ import json
|
|||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
||||||
from post_office.models import Email
|
from post_office.models import Email
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
from seahub.base.accounts import UserPermissions
|
from seahub.base.accounts import UserPermissions
|
||||||
from seahub.invitations.models import Invitation
|
from seahub.invitations.models import Invitation
|
||||||
from seahub.test_utils import BaseTestCase
|
from seahub.test_utils import BaseTestCase
|
||||||
|
from seahub.api2.permissions import CanInviteGuest
|
||||||
|
|
||||||
class InvitationsTest(BaseTestCase):
|
class InvitationsTest(BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -14,9 +14,12 @@ class InvitationsTest(BaseTestCase):
|
|||||||
self.endpoint = '/api/v2.1/invitations/'
|
self.endpoint = '/api/v2.1/invitations/'
|
||||||
self.username = self.user.username
|
self.username = self.user.username
|
||||||
|
|
||||||
|
@patch.object(CanInviteGuest, 'has_permission')
|
||||||
@patch.object(UserPermissions, 'can_invite_guest')
|
@patch.object(UserPermissions, 'can_invite_guest')
|
||||||
def test_can_add(self, mock_can_invite_guest):
|
def test_can_add(self, mock_can_invite_guest, mock_has_permission):
|
||||||
|
|
||||||
mock_can_invite_guest.return_val = True
|
mock_can_invite_guest.return_val = True
|
||||||
|
mock_has_permission.return_val = True
|
||||||
|
|
||||||
assert len(Invitation.objects.all()) == 0
|
assert len(Invitation.objects.all()) == 0
|
||||||
resp = self.client.post(self.endpoint, {
|
resp = self.client.post(self.endpoint, {
|
||||||
@@ -32,9 +35,12 @@ class InvitationsTest(BaseTestCase):
|
|||||||
|
|
||||||
assert len(Invitation.objects.all()) == 1
|
assert len(Invitation.objects.all()) == 1
|
||||||
|
|
||||||
|
@patch.object(CanInviteGuest, 'has_permission')
|
||||||
@patch.object(UserPermissions, 'can_invite_guest')
|
@patch.object(UserPermissions, 'can_invite_guest')
|
||||||
def test_can_send_mail(self, mock_can_invite_guest):
|
def test_can_send_mail(self, mock_can_invite_guest, mock_has_permission):
|
||||||
|
|
||||||
mock_can_invite_guest.return_val = True
|
mock_can_invite_guest.return_val = True
|
||||||
|
mock_has_permission.return_val = True
|
||||||
|
|
||||||
self.assertEqual(len(Email.objects.all()), 0)
|
self.assertEqual(len(Email.objects.all()), 0)
|
||||||
|
|
||||||
@@ -49,9 +55,12 @@ class InvitationsTest(BaseTestCase):
|
|||||||
self.assertRegexpMatches(Email.objects.all()[0].html_message,
|
self.assertRegexpMatches(Email.objects.all()[0].html_message,
|
||||||
json_resp['token'])
|
json_resp['token'])
|
||||||
|
|
||||||
|
@patch.object(CanInviteGuest, 'has_permission')
|
||||||
@patch.object(UserPermissions, 'can_invite_guest')
|
@patch.object(UserPermissions, 'can_invite_guest')
|
||||||
def test_can_list(self, mock_can_invite_guest):
|
def test_can_list(self, mock_can_invite_guest, mock_has_permission):
|
||||||
|
|
||||||
mock_can_invite_guest.return_val = True
|
mock_can_invite_guest.return_val = True
|
||||||
|
mock_has_permission.return_val = True
|
||||||
|
|
||||||
Invitation.objects.add(inviter=self.username, accepter='1@1.com')
|
Invitation.objects.add(inviter=self.username, accepter='1@1.com')
|
||||||
Invitation.objects.add(inviter=self.username, accepter='1@2.com')
|
Invitation.objects.add(inviter=self.username, accepter='1@2.com')
|
||||||
|
Reference in New Issue
Block a user