From 9c4a54931f46ff0abd84d0cf0723e29d94246cd2 Mon Sep 17 00:00:00 2001 From: xiez Date: Wed, 20 Jun 2012 19:39:21 +0800 Subject: [PATCH] Add org account feature --- base/accounts.py | 155 ++++++++++++- base/context_processors.py | 3 +- base/middleware.py | 1 - base/registration_urls.py | 13 ++ contacts/templates/contacts/contact_edit.html | 2 +- contacts/views.py | 10 +- group/views.py | 52 +++-- media/css/seahub.css | 11 + settings.py | 9 + .../templates/repo/anonymous_share_email.html | 2 +- subdomain/__init__.py | 0 subdomain/middleware.py | 37 +++ subdomain/models.py | 3 + subdomain/tests.py | 23 ++ subdomain/views.py | 1 + templates/add_user_form.html | 6 +- templates/admin_base.html | 11 +- templates/base.html | 17 +- templates/myhome.html | 5 + templates/myhome_base.html | 5 + templates/org_admin_base.html | 18 ++ templates/org_group_admin.html | 60 +++++ templates/org_info.html | 103 +++++++++ templates/org_seafadmin.html | 58 +++++ .../{useradmin.html => org_useradmin.html} | 16 +- .../registration/org_registration_form.html | 52 +++++ ...{group_admin.html => sys_group_admin.html} | 2 +- templates/sys_org_admin.html | 64 +++++ templates/{repos.html => sys_seafadmin.html} | 0 templates/sys_useradmin.html | 87 +++++++ templates/user_add_email.html | 19 ++ thirdpart/auth/views.py | 1 - thirdpart/seaserv/service.py | 4 + urls.py | 33 ++- utils.py | 23 +- views.py | 218 ++++++++++++++++-- 36 files changed, 1039 insertions(+), 85 deletions(-) create mode 100644 subdomain/__init__.py create mode 100644 subdomain/middleware.py create mode 100644 subdomain/models.py create mode 100644 subdomain/tests.py create mode 100644 subdomain/views.py create mode 100644 templates/org_admin_base.html create mode 100644 templates/org_group_admin.html create mode 100644 templates/org_info.html create mode 100644 templates/org_seafadmin.html rename templates/{useradmin.html => org_useradmin.html} (83%) create mode 100644 templates/registration/org_registration_form.html rename templates/{group_admin.html => sys_group_admin.html} (95%) create mode 100644 templates/sys_org_admin.html rename templates/{repos.html => sys_seafadmin.html} (100%) create mode 100644 templates/sys_useradmin.html create mode 100644 templates/user_add_email.html diff --git a/base/accounts.py b/base/accounts.py index 4f64b92ddf..f26a901c64 100644 --- a/base/accounts.py +++ b/base/accounts.py @@ -9,7 +9,7 @@ from django.contrib.sites.models import Site from auth.models import get_hexdigest, check_password from auth import authenticate, login from registration import signals -from registration.forms import RegistrationForm +#from registration.forms import RegistrationForm from registration.models import RegistrationProfile from seaserv import ccnet_rpc, get_ccnetuser @@ -29,13 +29,15 @@ def convert_to_ccnetuser(emailuser): ccnetuser.is_staff = emailuser.props.is_staff ccnetuser.is_active = emailuser.props.is_active ccnetuser.ctime = emailuser.props.ctime - + ccnetuser.org = emailuser.org + return ccnetuser class CcnetUser(object): is_staff = False is_active = False objects = UserManager() + org = None def __init__(self, username, raw_password): self.username = username @@ -225,12 +227,6 @@ class RegistrationBackend(object): # login the user activated.backend='auth.backends.ModelBackend' login(request, activated) - # TODO: user.user_id should be change - try: - if request.user.user_id: - ccnet_rpc.add_client(ccnet_user_id) - except: - pass return activated @@ -323,3 +319,146 @@ class RegistrationForm(forms.Form): raise forms.ValidationError(_("The two password fields didn't match.")) return self.cleaned_data +class OrgRegistrationForm(RegistrationForm): + """ + Form for registering a business user account. + + Validates that the requested email is not already in use, and + requires the password to be entered twice to catch typos. + """ + + org_name = forms.CharField(max_length=256, + widget=forms.TextInput(), + label=_("Organization Name")) + url_prefix = forms.RegexField(label=_("Url Prefix"), max_length=20, + regex=r'^[a-z0-9]+$', + error_message=_("This value must contain only letters or numbers.")) + + def clean_url_prefix(self): + url_prefix = self.cleaned_data['url_prefix'] + org = ccnet_rpc.get_org_by_url_prefix(url_prefix) + if not org: + return url_prefix + else: + raise forms.ValidationError(_("A organization with this url prefix already")) + +class OrgRegistrationBackend(object): + def register(self, request, **kwargs): + """ + Given a username, email address and password, register a new + user account, which will initially be inactive. + + Along with the new ``User`` object, a new + ``registration.models.RegistrationProfile`` will be created, + tied to that ``User``, containing the activation key which + will be used for this account. + + An email will be sent to the supplied email address; this + email should contain an activation link. The email will be + rendered using two templates. See the documentation for + ``RegistrationProfile.send_activation_email()`` for + information about these templates and the contexts provided to + them. + + After the ``User`` and ``RegistrationProfile`` are created and + the activation email is sent, the signal + ``registration.signals.user_registered`` will be sent, with + the new ``User`` as the keyword argument ``user`` and the + class of this backend as the sender. + + """ + email, password = kwargs['email'], kwargs['password1'] + username = email + org_name, url_prefix = kwargs['org_name'], kwargs['url_prefix'] + + if Site._meta.installed: + site = Site.objects.get_current() + else: + site = RequestSite(request) + + + if settings.ACTIVATE_AFTER_REGISTRATION: + # since user will be activated after registration, + # so we will not use email sending, just create acitvated user + new_user = RegistrationProfile.objects.create_active_user(username, email, + password, site, + send_email=False) + # create orgnization account + try: + ccnet_rpc.create_org(org_name, url_prefix, username) + except SearpcError, e: + pass + else: + # login the user + new_user.backend='auth.backends.ModelBackend' + login(request, new_user) + else: + # create inactive user, user can be activated by admin, or through activated email + new_user = RegistrationProfile.objects.create_inactive_user(username, email, + password, site, + send_email=settings.REGISTRATION_SEND_MAIL) + + signals.user_registered.send(sender=self.__class__, + user=new_user, + request=request) + return new_user + + def activate(self, request, activation_key): + """ + Given an an activation key, look up and activate the user + account corresponding to that key (if possible). + + After successful activation, the signal + ``registration.signals.user_activated`` will be sent, with the + newly activated ``User`` as the keyword argument ``user`` and + the class of this backend as the sender. + + """ + activated = RegistrationProfile.objects.activate_user(activation_key) + if activated: + signals.user_activated.send(sender=self.__class__, + user=activated, + request=request) + # login the user + activated.backend='auth.backends.ModelBackend' + login(request, activated) + + return activated + + def registration_allowed(self, request): + """ + Indicate whether account registration is currently permitted, + based on the value of the setting ``REGISTRATION_OPEN``. This + is determined as follows: + + * If ``REGISTRATION_OPEN`` is not specified in settings, or is + set to ``True``, registration is permitted. + + * If ``REGISTRATION_OPEN`` is both specified and set to + ``False``, registration is not permitted. + + """ + return getattr(settings, 'REGISTRATION_OPEN', True) + + def get_form_class(self, request): + """ + Return the default form class used for user registration. + + """ + return RegistrationForm + + def post_registration_redirect(self, request, user): + """ + Return the name of the URL to redirect to after successful + user registration. + + """ + return ('registration_complete', (), {}) + + def post_activation_redirect(self, request, user): + """ + Return the name of the URL to redirect to after successful + account activation. + + """ + return ('myhome', (), {}) diff --git a/base/context_processors.py b/base/context_processors.py index 772989ec9f..4944bf1b6e 100644 --- a/base/context_processors.py +++ b/base/context_processors.py @@ -15,5 +15,6 @@ def base(request): """ return { 'seafile_version': settings.SEAFILE_VERSION, - 'seahub_title': settings.SEAHUB_TITLE, + 'seahub_title': settings.SEAHUB_TITLE, + 'account_type': settings.ACCOUNT_TYPE, } diff --git a/base/middleware.py b/base/middleware.py index 490d62859b..2a146c640e 100644 --- a/base/middleware.py +++ b/base/middleware.py @@ -47,4 +47,3 @@ class InfobarMiddleware(object): def process_response(self, request, response): return response - diff --git a/base/registration_urls.py b/base/registration_urls.py index 07c9eac8fc..5987dd8649 100644 --- a/base/registration_urls.py +++ b/base/registration_urls.py @@ -6,13 +6,20 @@ from registration.views import activate from registration.views import register from seahub.base.accounts import RegistrationForm +from seahub.base.accounts import OrgRegistrationForm reg_dict = { 'backend': 'seahub.base.accounts.RegistrationBackend', 'form_class': RegistrationForm, } +org_reg_dict = { 'backend': 'seahub.base.accounts.OrgRegistrationBackend', + 'form_class': OrgRegistrationForm, + 'template_name': 'registration/org_registration_form.html', + } + if settings.ACTIVATE_AFTER_REGISTRATION == True: reg_dict['success_url'] = settings.SITE_ROOT + org_reg_dict['success_url'] = settings.SITE_ROOT urlpatterns = patterns('', url(r'^activate/complete/$', @@ -40,5 +47,11 @@ urlpatterns = patterns('', direct_to_template, { 'template': 'registration/registration_closed.html' }, name='registration_disallowed'), + + url(r'^business/register/$', + register, + org_reg_dict, + name='registration_register'), + (r'', include('registration.auth_urls')), ) diff --git a/contacts/templates/contacts/contact_edit.html b/contacts/templates/contacts/contact_edit.html index c5b57413d2..868cacd443 100644 --- a/contacts/templates/contacts/contact_edit.html +++ b/contacts/templates/contacts/contact_edit.html @@ -8,7 +8,7 @@
{{ form.user_email.as_hidden }} - {{ form.contact_email }} + {{ form.contact_name }} diff --git a/contacts/views.py b/contacts/views.py index b26bfc3f20..4a9265f04f 100644 --- a/contacts/views.py +++ b/contacts/views.py @@ -35,8 +35,12 @@ def contact_add(request): elif contact_email == request.user.username: error_msg = u"不能添加自己为联系人" elif Contact.objects.filter(user_email=request.user.username, - contact_email=contact_email).count() > 0: + contact_email=contact_email).count() > 0: error_msg = u"联系人列表中已有该用户" + elif request.user.org and \ + not ccnet_rpc.org_user_exists(request.user.org.org_id, + contact_email): + error_msg = u"当前企业不存在该用户" else: contact = Contact() contact.user_email = request.user.username @@ -69,9 +73,9 @@ def contact_edit(request): emailuser = ccnet_rpc.get_emailuser(contact_email) if not emailuser: error_msg = u"用户不存在" - elif cmp(contact_email, request.user.username) == 0: + elif contact_email == request.user.username: error_msg = u"不能添加自己为联系人" - elif cmp(old_contact_email, contact_email) != 0 and \ + elif old_contact_email != contact_email and \ Contact.objects.filter(user_email=request.user.username, contact_email=contact_email).count() > 0: error_msg = u"联系人列表中已有该用户" diff --git a/group/views.py b/group/views.py index fa4bc0de4c..5e20f0758e 100644 --- a/group/views.py +++ b/group/views.py @@ -25,8 +25,12 @@ def group_list(request): return go_error(request, u'小组名称只能包含中英文字符,数字及下划线') try: - ccnet_rpc.create_group(group_name.encode('utf-8'), + group_id = ccnet_rpc.create_group(group_name.encode('utf-8'), request.user.username) + # TODO: transaction? + if request.user.org and group_id > 0: + ccnet_rpc.add_org_group(request.user.org.org_id, + group_id) except SearpcError, e: error_msg = e.msg return go_error(request, error_msg) @@ -62,11 +66,17 @@ def group_remove(request, group_id): try: ccnet_rpc.remove_group(group_id_int, request.user.username) seafserv_threaded_rpc.remove_repo_group(group_id_int, None) + + if request.user.org: + ccnet_rpc.remove_org_group(request.user.org.org_id, + group_id_int) except SearpcError, e: return go_error(request, e.msg) - if request.GET.get('src', '') == 'groupadmin': - return HttpResponseRedirect(reverse('group_admin')) + if request.GET.get('src', '') == 'orggroupadmin': + return HttpResponseRedirect(reverse('org_group_admin')) + elif request.GET.get('src', '') == 'sysgroupadmin': + return HttpResponseRedirect(reverse('sys_group_admin')) else: return HttpResponseRedirect(reverse('group_list', args=[])) @@ -178,17 +188,31 @@ def group_members(request, group_id): continue member_name_dict[member_name] = member_name - for member_name in member_name_dict.keys(): - if not validate_emailuser(member_name): - err_msg = u'用户 %s 不存在' % member_name - return go_error(request, err_msg) - else: - try: - ccnet_rpc.group_add_member(group_id_int, - request.user.username, - member_name) - except SearpcError, e: - return go_error(request, e.msg) + if request.user.org: + for member_name in member_name_dict.keys(): + if not ccnet_rpc.org_user_exists(request.user.org.org_id, + member_name): + err_msg = u'当前企业不存在 %s 用户' % member_name + return go_error(request, err_msg) + else: + try: + ccnet_rpc.group_add_member(group_id_int, + request.user.username, + member_name) + except SearpcError, e: + return go_error(request, e.msg) + else: + for member_name in member_name_dict.keys(): + if not validate_emailuser(member_name): + err_msg = u'用户 %s 不存在' % member_name + return go_error(request, err_msg) + else: + try: + ccnet_rpc.group_add_member(group_id_int, + request.user.username, + member_name) + except SearpcError, e: + return go_error(request, e.msg) members = ccnet_rpc.get_group_members(group_id_int) contacts = Contact.objects.filter(user_email=request.user.username) diff --git a/media/css/seahub.css b/media/css/seahub.css index 734410e2f6..9b76581589 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -365,3 +365,14 @@ h2.repo-history { /* notification admin */ .cur-note { color: red; font-size: 75%; } + +/* org */ +.org-member .avatar { + border-radius: 2px; + -moz-border-radius: 2px; + margin-right: 5px; +} +.org-member .avatar, +.org-member-name { + vertical-align:middle; +} diff --git a/settings.py b/settings.py index 7f4cf0fffe..96c1e654cd 100644 --- a/settings.py +++ b/settings.py @@ -68,6 +68,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.messages.middleware.MessageMiddleware', 'auth.middleware.AuthenticationMiddleware', 'seahub.base.middleware.InfobarMiddleware', + 'seahub.subdomain.middleware.SubdomainMiddleware', # 'seahub.base.middleware.UseridMiddleware', ) @@ -110,6 +111,7 @@ INSTALLED_APPS = ( 'seahub.contacts', 'seahub.group', 'seahub.share', + 'seahub.subdomain', ) AUTHENTICATION_BACKENDS = ( @@ -160,6 +162,13 @@ FILEEXT_ICON_MAP = { 'default' : 'file-icon-24.png', } +SITE_SUBDOMAIN = 'cloud' +SITE_BASE_NAME = 'seafile.com.cn' if not DEBUG else 'localhost.localdomain' +SESSION_COOKIE_DOMAIN = '.' + SITE_BASE_NAME + +# account type is `personal` or `business` +ACCOUNT_TYPE = 'personal' + try: import local_settings except ImportError: diff --git a/share/templates/repo/anonymous_share_email.html b/share/templates/repo/anonymous_share_email.html index bf7b8fce1f..a5c1c023a3 100644 --- a/share/templates/repo/anonymous_share_email.html +++ b/share/templates/repo/anonymous_share_email.html @@ -1,5 +1,5 @@ {% autoescape off %} -亲爱的:{{ anon_email }} 您好! +亲爱的 {{ anon_email }}: {{ email }} 在SeaCloud上共享了一个同步目录给你,请点击以下链接查看: diff --git a/subdomain/__init__.py b/subdomain/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/subdomain/middleware.py b/subdomain/middleware.py new file mode 100644 index 0000000000..7ad20a6cf1 --- /dev/null +++ b/subdomain/middleware.py @@ -0,0 +1,37 @@ +from django.http import HttpResponseRedirect, Http404 + +from seahub.settings import SITE_BASE_NAME, SITE_SUBDOMAIN + +class SubdomainMiddleware(object): + + def process_request(self, request): + if not request.user.is_authenticated(): + return None + + host = request.META.get('HTTP_HOST', '') + http_or_https = request.is_secure() and 'https://' or 'http://' + has_subdomain = True if host.replace(SITE_BASE_NAME, '', 1).find('.') >= 0 else False + full_path = request.get_full_path() + + if request.user.org: + # business account + url_prefix = request.user.org.url_prefix + if not has_subdomain: + host = request.user.org.url_prefix + '.' + host + return HttpResponseRedirect(http_or_https + host + full_path) + elif host.split('.')[0] != url_prefix: + host = url_prefix + '.' + '.'.join(host.split('.')[1:]) + return HttpResponseRedirect(http_or_https + host + full_path) + else: + # personal account + if not has_subdomain: + host = SITE_SUBDOMAIN + '.' + host + return HttpResponseRedirect(http_or_https + host + full_path) + elif host.split('.')[0] != SITE_SUBDOMAIN: + host = SITE_SUBDOMAIN + '.' + '.'.join(host.split('.')[1:]) + return HttpResponseRedirect(http_or_https + host + full_path) + + return None + + def process_response(self, request, response): + return response diff --git a/subdomain/models.py b/subdomain/models.py new file mode 100644 index 0000000000..71a8362390 --- /dev/null +++ b/subdomain/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/subdomain/tests.py b/subdomain/tests.py new file mode 100644 index 0000000000..2247054b35 --- /dev/null +++ b/subdomain/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/subdomain/views.py b/subdomain/views.py new file mode 100644 index 0000000000..60f00ef0ef --- /dev/null +++ b/subdomain/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/templates/add_user_form.html b/templates/add_user_form.html index 9526b070ad..f3a20a900d 100644 --- a/templates/add_user_form.html +++ b/templates/add_user_form.html @@ -1,4 +1,4 @@ -{% extends "admin_base.html" %} +{% extends base_template %} {% block title %}添加用户{% endblock %} {% block nav_useradmin_class %}class="cur"{% endblock %} {% block main_panel %} @@ -15,9 +15,9 @@ {{ form.email.errors }} {{ form.password1.errors }} {{ form.password2.errors }} -

+

- +
{% endblock %} diff --git a/templates/admin_base.html b/templates/admin_base.html index 6f6e8b5bdc..d7272597bf 100644 --- a/templates/admin_base.html +++ b/templates/admin_base.html @@ -4,16 +4,19 @@ diff --git a/templates/base.html b/templates/base.html index 42b0ae3bd7..b6cc2c70ea 100644 --- a/templates/base.html +++ b/templates/base.html @@ -25,10 +25,15 @@
- {% if request.user.is_staff %} + {% if request.user.is_staff or request.user.org.is_staff %}
- 管理员控制台 - 我的帐号 + {% if request.user.is_staff %} + 管理员控制台 + 我的帐号
{% endif %} @@ -43,9 +48,13 @@ --> 退出 {% else %} - 登录 + 登录 + {% if account_type == 'business' %} + 注册 + {% else %} 注册 {% endif %} + {% endif %}
diff --git a/templates/myhome.html b/templates/myhome.html index def6de8736..53413f51e2 100644 --- a/templates/myhome.html +++ b/templates/myhome.html @@ -4,6 +4,11 @@ {% block nav_myhome_class %}class="cur"{% endblock %} {% block left_panel %} +{% if request.user.org %} +

所属企业

+

{{ request.user.org.org_name }}

+{% endif %} +

已用空间

{{ quota_usage|filesizeformat }} / 2 GB

diff --git a/templates/myhome_base.html b/templates/myhome_base.html index be32727bbe..4a34bddd1f 100644 --- a/templates/myhome_base.html +++ b/templates/myhome_base.html @@ -5,6 +5,11 @@
  • 我的页面
  • + {% if request.user.org %} +
  • + 企业 +
  • + {% endif %}
  • 小组
  • diff --git a/templates/org_admin_base.html b/templates/org_admin_base.html new file mode 100644 index 0000000000..c0ece661b1 --- /dev/null +++ b/templates/org_admin_base.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% block top_bar_manager_class %} class="cur"{% endblock %} +{% block nav %} + +{% endblock %} diff --git a/templates/org_group_admin.html b/templates/org_group_admin.html new file mode 100644 index 0000000000..b838753bf7 --- /dev/null +++ b/templates/org_group_admin.html @@ -0,0 +1,60 @@ +{% extends "org_admin_base.html" %} +{% load seahub_tags %} + +{% block nav_groupadmin_class %}class="cur"{% endblock %} + +{% block right_panel %} +

    所有小组

    +{% if groups %} + + + + + + + + {% for group in groups %} + + + + + + + {% endfor %} +
    名字创建者创建时间操作
    {{ group.props.group_name }}{{ group.props.creator_name }}{{ group.props.timestamp|tsstr_sec }}
    + +
    + {% if current_page != 1 %} + 上一页 + {% endif %} + {% if page_next %} + 下一页 + {% endif %} + 每页: + {% if per_page == 25 %} + 25 + {% else %} + 25 + {% endif %} + {% if per_page == 50 %} + 50 + {% else %} + 50 + {% endif %} + {% if per_page == 100 %} + 100 + {% else %} + 100 + {% endif %} +
    +{% else %} +

    暂无

    +{% endif %} +{% endblock %} + +{% block extra_script %} + +{% endblock %} + diff --git a/templates/org_info.html b/templates/org_info.html new file mode 100644 index 0000000000..dc0916cb13 --- /dev/null +++ b/templates/org_info.html @@ -0,0 +1,103 @@ +{% extends "myhome_base.html" %} +{% load seahub_tags avatar_tags %} + +{% block nav_org_class %}class="cur"{% endblock %} + +{% block main_panel %} +

    {{ org.org_name }}

    +
    +

    企业成员

    +{% if org_users %} + +{% else %} +

    暂无

    +{% endif %} + +{% if is_join %} +

    操作

    + +{% endif %} +
    + +
    +

    企业里公开的同步目录

    +{% if repos %} + + + + + + + + + {% for repo in repos %} + + + + + + + {% endfor %} +
    名字描述共享来源操作
    {{ repo.props.name }}{{ repo.props.desc }}{{ repo.share_from }} + 下载 + {% if is_staff or repo.share_from_me %} + + {% endif %} +
    +{% else %} +

    暂无

    +{% endif %} + +

    企业里的小组

    +{% if groups %} + + + + + + + + {% for group in groups %} + + + + + + {% endfor %} + +
    名字创建者创建时间
    {{ group.group_name }}{{ group.creator_name }}{{ group.timestamp|tsstr_sec }}
    +{% else %} +

    暂无

    +{% endif %} +
    +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/templates/org_seafadmin.html b/templates/org_seafadmin.html new file mode 100644 index 0000000000..114e56b9f1 --- /dev/null +++ b/templates/org_seafadmin.html @@ -0,0 +1,58 @@ +{% extends "org_admin_base.html" %} +{% block nav_seafadmin_class %}class="cur"{% endblock %} + +{% block right_panel %} +

    所有同步目录

    +{% if repos %} + + + + + + + + {% for repo in repos %} + + + + + + + {% endfor %} +
    名字拥有者描述操作
    {{ repo.props.name }}{{ repo.owner}}{{ repo.props.desc }}
    + +
    + {% if current_page != 1 %} + 上一页 + {% endif %} + {% if page_next %} + 下一页 + {% endif %} + 每页: + {% if per_page == 25 %} + 25 + {% else %} + 25 + {% endif %} + {% if per_page == 50 %} + 50 + {% else %} + 50 + {% endif %} + {% if per_page == 100 %} + 100 + {% else %} + 100 + {% endif %} +
    +{% else %} +

    暂无

    +{% endif %} +{% endblock %} + +{% block extra_script %} + +{% endblock %} + diff --git a/templates/useradmin.html b/templates/org_useradmin.html similarity index 83% rename from templates/useradmin.html rename to templates/org_useradmin.html index f804e7ec47..d78f7c6678 100644 --- a/templates/useradmin.html +++ b/templates/org_useradmin.html @@ -1,4 +1,4 @@ -{% extends "admin_base.html" %} +{% extends "org_admin_base.html" %} {% block nav_useradmin_class %}class="cur"{% endblock %} {% block left_panel %} @@ -10,6 +10,18 @@ {% block right_panel %} +{% if messages %} + +{% endif %}

    所有用户

    @@ -32,7 +44,7 @@ {% endif %} {% if not user.is_self %} - + {% endif %} diff --git a/templates/registration/org_registration_form.html b/templates/registration/org_registration_form.html new file mode 100644 index 0000000000..91bd56fc7b --- /dev/null +++ b/templates/registration/org_registration_form.html @@ -0,0 +1,52 @@ +{% extends "myhome_base.html" %} +{% block title %}注册{% endblock %} +{% block main_panel %} +
    +{% if request.user.is_authenticated %} +

    欢迎回来,您已登录。

    +{% else %} +

    注册企业帐号

    +
    + + {{ form.email }} + + {{ form.password1 }} + + {{ form.password2 }} + + {{ form.org_name}} + 域名前缀: + {{ form.url_prefix }} +

    + {{ form.email.errors }} + {{ form.password1.errors }} + {{ form.password2.errors }} + {{ form.url_prefix.errors }} + + +{% endif %} +
    +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/templates/group_admin.html b/templates/sys_group_admin.html similarity index 95% rename from templates/group_admin.html rename to templates/sys_group_admin.html index dcc9cd27e2..804f7d599a 100644 --- a/templates/group_admin.html +++ b/templates/sys_group_admin.html @@ -18,7 +18,7 @@ - + {% endfor %}
    {{ group.props.group_name }} {{ group.props.creator_name }} {{ group.props.timestamp|tsstr_sec }}
    diff --git a/templates/sys_org_admin.html b/templates/sys_org_admin.html new file mode 100644 index 0000000000..c37ea9a4b4 --- /dev/null +++ b/templates/sys_org_admin.html @@ -0,0 +1,64 @@ +{% extends "admin_base.html" %} +{% load seahub_tags %} + +{% block nav_orgadmin_class %}class="cur"{% endblock %} + +{% block right_panel %} +

    所有企业

    +{% if orgs %} + + + + + + + + + {% for org in orgs %} + + + + + + + + {% endfor %} +
    名字域名前缀创建者创建时间操作
    {{ org.org_name }}{{ org.url_prefix }}{{ org.creator }}{{ org.ctime|tsstr_sec }}
    + + +{% else %} +

    暂无

    +{% endif %} +{% endblock %} + +{% block extra_script %} + +{% endblock %} + diff --git a/templates/repos.html b/templates/sys_seafadmin.html similarity index 100% rename from templates/repos.html rename to templates/sys_seafadmin.html diff --git a/templates/sys_useradmin.html b/templates/sys_useradmin.html new file mode 100644 index 0000000000..092cd0e119 --- /dev/null +++ b/templates/sys_useradmin.html @@ -0,0 +1,87 @@ +{% extends "admin_base.html" %} + +{% block nav_useradmin_class %}class="cur"{% endblock %} +{% block left_panel %} +

    操作

    + +{% endblock %} + + +{% block right_panel %} +{% if messages %} + +{% endif %} + +

    所有用户

    + + + + + + + + + {% for user in users %} + + + {% if user.props.is_active %} + + {% else %} + + {% endif %} + {% if user.is_org_user %} + + {% else %} + + {% endif %} + + + {% endfor %} +
    邮箱是否激活是否企业帐号操作
    {{ user.props.email }}已激活 + {% if user.profile %} + + {% endif %} + {% if not user.is_self %} + + {% endif %} +
    + +
    +

    的新角色 (即 MyClient 等):

    +
    + +
    +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/templates/user_add_email.html b/templates/user_add_email.html new file mode 100644 index 0000000000..879209a530 --- /dev/null +++ b/templates/user_add_email.html @@ -0,0 +1,19 @@ +{% autoescape off %} +亲爱的 {{ email }}: +{% if org %} +{{ user }} 在 SeaCloud 云存储上将您加入到企业 {{ org.org_name }}! +{% else %} +{{ user }} 将您加入 SeaCloud 云存储! +{% endif %} +以下是您的登录信息: +用户名: {{ email }} +密码: {{ password }} + +请点击以下链接登录: +{{ protocol }}://{{ domain }}{% url auth_login %} + +感谢使用我们的网站! + +Seafile团队 + +{% endautoescape %} diff --git a/thirdpart/auth/views.py b/thirdpart/auth/views.py index 6983f28468..32937dedf5 100644 --- a/thirdpart/auth/views.py +++ b/thirdpart/auth/views.py @@ -50,7 +50,6 @@ def login(request, template_name='registration/login.html', # Okay, security checks complete. Log the user in. auth_login(request, form.get_user()) - if request.session.test_cookie_worked(): request.session.delete_test_cookie() diff --git a/thirdpart/seaserv/service.py b/thirdpart/seaserv/service.py index 2455f327ab..3466f0d460 100644 --- a/thirdpart/seaserv/service.py +++ b/thirdpart/seaserv/service.py @@ -304,6 +304,10 @@ def get_ccnetuser(username=None, userid=None): emailuser = ccnet_rpc.get_emailuser_by_id(userid) if not emailuser: return None + + # Check whether is business account + org = ccnet_rpc.get_org_by_user(emailuser.email) + emailuser.org = org # And convert to ccnetuser from seahub.base.accounts import convert_to_ccnetuser diff --git a/urls.py b/urls.py index e7b17cb14d..4d31077304 100644 --- a/urls.py +++ b/urls.py @@ -3,12 +3,13 @@ from django.conf import settings from django.views.generic.simple import direct_to_template from seahub.views import root, peers, myhome, \ - repo, repo_history, modify_token, remove_repo, seafadmin, useradmin, \ - activate_user, user_add, user_remove, \ + repo, repo_history, modify_token, remove_repo, sys_seafadmin, sys_useradmin, \ + org_seafadmin, org_useradmin, org_group_admin, org_remove, \ + activate_user, user_add, user_remove, sys_group_admin, sys_org_admin, \ ownerhome, repo_history_dir, repo_history_revert, \ user_info, repo_set_access_property, repo_access_file, \ - repo_remove_share, repo_download, \ - seafile_access_check, back_local, group_admin, repo_history_changes + repo_remove_share, repo_download, org_info, \ + seafile_access_check, back_local, repo_history_changes from seahub.notifications.views import notification_list from seahub.share.views import share_admin @@ -50,25 +51,33 @@ urlpatterns = patterns('', (r'^repo/(?P[^/]+)/(?P[^/]+)/$', repo_access_file), (r'^download/repo/$', repo_download), (r'^seafile_access_check/$', seafile_access_check), + url(r'^org/remove/(?P[\d]+)/$', org_remove, name="org_remove"), + (r'^org/$', org_info), + (r'^back/local/$', back_local), - (r'^seafadmin/$', seafadmin), - url(r'^useradmin/$', useradmin, name='useradmin'), (r'^useradmin/add/$', user_add), + (r'^useradmin/remove/(?P[^/]+)/$', user_remove), (r'^useradmin/info/(?P[^/]+)/$', user_info), -# (r'^useradmin/(?P[^/]+)/role/add/$', role_add), -# (r'^useradmin/(?P[^/]+)/role/remove/$', role_remove), - (r'^useradmin/(?P[^/]+)/user/remove/$', user_remove), (r'^useradmin/activate/(?P[^/]+)/$', activate_user), + ### Apps ### (r'^avatar/', include('avatar.urls')), (r'^notification/', include('notifications.urls')), - url(r'^notificationadmin/', notification_list, name='notification_list'), + url(r'^sys/notificationadmin/', notification_list, name='notification_list'), (r'^contacts/', include('contacts.urls')), (r'^group/', include('seahub.group.urls')), - url(r'^groupadmin/$', group_admin, name='group_admin'), (r'^profile/', include('seahub.profile.urls')), - (r'^back/local/$', back_local), + ### SeaHub admin ### + (r'^sys/seafadmin/$', sys_seafadmin), + url(r'^sys/useradmin/$', sys_useradmin, name='sys_useradmin'), + url(r'^sys/orgadmin/$', sys_org_admin, name='sys_org_admin'), + url(r'^sys/groupadmin/$', sys_group_admin, name='sys_group_admin'), + + ### Org admin ### + (r'^seafadmin/$', org_seafadmin), + url(r'^useradmin/$', org_useradmin, name='org_useradmin'), + url(r'^groupadmin/$', org_group_admin, name='org_group_admin'), ) if settings.DEBUG: diff --git a/utils.py b/utils.py index ebf8fe44ce..b42b9e54c2 100644 --- a/utils.py +++ b/utils.py @@ -9,7 +9,7 @@ from django.utils.hashcompat import sha_constructor def go_permission_error(request, msg=None): """ - return permisson error page + Return permisson error page. """ return render_to_response('permission_error.html', { @@ -18,7 +18,7 @@ def go_permission_error(request, msg=None): def go_error(request, msg=None): """ - return normal error page + Return normal error page. """ return render_to_response('error.html', { @@ -27,20 +27,15 @@ def go_error(request, msg=None): def list_to_string(l): """ - return string of a list + Return string of a list. """ - tmp_str = '' - for e in l[:-1]: - tmp_str = tmp_str + e + ', ' - tmp_str = tmp_str + l[-1] - - return tmp_str + return ','.join(l) def get_httpserver_root(): """ Get seafile http server address and port from settings.py, - and cut out last '/' + and cut out last '/'. """ if settings.HTTP_SERVER_ROOT[-1] == '/': @@ -52,7 +47,7 @@ def get_httpserver_root(): def get_ccnetapplet_root(): """ Get ccnet applet address and port from settings.py, - and cut out last '/' + and cut out last '/'. """ if settings.CCNET_APPLET_ROOT[-1] == '/': @@ -63,17 +58,17 @@ def get_ccnetapplet_root(): def gen_token(): """ - Generate short token used for owner to access repo file + Generate short token used for owner to access repo file. """ - token = sha_constructor(settings.SECRET_KEY + unicode(time.time())).hexdigest()[::8] + token = sha_constructor(settings.SECRET_KEY + unicode(time.time())).hexdigest()[:5] return token def validate_group_name(group_name): """ Check whether group name is valid. - A valid group name only contains alphanumeric character + A valid group name only contains alphanumeric character. """ return re.match('^\w+$', group_name, re.U) diff --git a/views.py b/views.py index 43486ea3c1..94e300e57b 100644 --- a/views.py +++ b/views.py @@ -2,13 +2,16 @@ import settings import stat import simplejson as json +import sys from urllib import quote from django.core.urlresolvers import reverse +from django.core.mail import send_mail from django.contrib import messages +from django.contrib.sites.models import Site, RequestSite from django.db import IntegrityError from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.shortcuts import render_to_response, redirect -from django.template import RequestContext +from django.template import Context, loader, RequestContext from django.views.decorators.csrf import csrf_protect from auth.decorators import login_required @@ -713,7 +716,7 @@ def mypeers(request): cid = get_user_cid(request.user) @login_required -def seafadmin(request): +def sys_seafadmin(request): if not request.user.is_staff: raise Http404 @@ -728,6 +731,7 @@ def seafadmin(request): repos_all = seafserv_threaded_rpc.get_repo_list(per_page * (current_page -1), per_page + 1) + repos = repos_all[:per_page] if len(repos_all) == per_page + 1: @@ -742,7 +746,7 @@ def seafadmin(request): repo.owner = None return render_to_response( - 'repos.html', { + 'sys_seafadmin.html', { 'repos': repos, 'current_page': current_page, 'prev_page': current_page-1, @@ -753,17 +757,80 @@ def seafadmin(request): context_instance=RequestContext(request)) @login_required -def useradmin(request): +def org_seafadmin(request): + if not request.user.org: + raise Http404 + + # 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', '25')) + except ValueError: + current_page = 1 + per_page = 25 + + repos_all = seafserv_threaded_rpc.get_org_repo_list(request.user.org.org_id, + per_page * (current_page -1), + per_page + 1) + + repos = repos_all[:per_page] + + if len(repos_all) == per_page + 1: + page_next = True + else: + page_next = False + + for repo in repos: + try: + repo.owner = seafserv_threaded_rpc.get_repo_owner(repo.props.id) + except: + repo.owner = None + + return render_to_response( + 'org_seafadmin.html', { + 'repos': repos, + '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)) + +@login_required +def sys_useradmin(request): if not request.user.is_staff: raise Http404 users = ccnet_rpc.get_emailusers(-1,-1) + for user in users: if user.props.id == request.user.id: user.is_self = True + # TODO: may add new is_org_user rpc + user.is_org_user = True if ccnet_rpc.get_org_by_user(user.email) else False return render_to_response( - 'useradmin.html', { + 'sys_useradmin.html', { + 'users': users, + }, + context_instance=RequestContext(request)) + +@login_required +def org_useradmin(request): + if not request.user.org.is_staff: + raise Http404 + + users = ccnet_rpc.get_org_emailusers(request.user.org.url_prefix, + 0, sys.maxint) + + for user in users: + if user.props.id == request.user.id: + user.is_self = True + user.is_org_user = True + + return render_to_response( + 'org_useradmin.html', { 'users': users, }, context_instance=RequestContext(request)) @@ -834,13 +901,18 @@ def user_info(request, email): def user_remove(request, user_id): """The user id is emailuser id.""" - if not request.user.is_staff: + if not request.user.is_staff and not request.user.org.is_staff: raise Http404 ccnetuser = get_ccnetuser(userid=int(user_id)) + if ccnetuser.org: + ccnet_rpc.remove_org_user(ccnetuser.org.org_id, ccnetuser.username) ccnetuser.delete() - - return HttpResponseRedirect(reverse('useradmin')) + + if request.user.is_staff: + return HttpResponseRedirect(reverse('sys_useradmin')) + else: + return HttpResponseRedirect(reverse('org_useradmin')) @login_required def activate_user(request, user_id): @@ -855,13 +927,37 @@ def activate_user(request, user_id): return HttpResponseRedirect(reverse('useradmin')) +def send_user_add_mail(request, email, password): + """ Send email when add new user """ + + use_https = request.is_secure() + domain = RequestSite(request).domain + + t = loader.get_template('user_add_email.html') + c = { + 'user': request.user.username, + 'org': request.user.org, + 'email': email, + 'password': password, + 'domain': domain, + 'protocol': use_https and 'https' or 'http', + } + try: + send_mail(u'SeaCloud注册信息', t.render(Context(c)), + None, [email], fail_silently=False) + messages.add_message(request, messages.INFO, email) + except: + messages.add_message(request, messages.ERROR, email) + @login_required def user_add(request): """Add a user""" - if not request.user.is_staff: + if not request.user.is_staff and not request.user.org.is_staff: raise Http404 + base_template = 'org_admin_base.html' if request.user.org else 'admin_base.html' + if request.method == 'POST': form = AddUserForm(request.POST) if form.is_valid(): @@ -872,12 +968,24 @@ def user_add(request): ccnetuser.is_active = True ccnetuser.save() - return HttpResponseRedirect(reverse('useradmin', args=[])) + if request.user.org: + org_id = request.user.org.org_id + ccnet_rpc.add_org_user(org_id, email, 0) + if hasattr(settings, 'EMAIL_HOST'): + send_user_add_mail(request, email, password) + + return HttpResponseRedirect(reverse('org_useradmin')) + else: + if hasattr(settings, 'EMAIL_HOST'): + send_user_add_mail(request, email, password) + + return HttpResponseRedirect(reverse('sys_useradmin', args=[])) else: form = AddUserForm() return render_to_response("add_user_form.html", { - 'form': form, + 'form': form, + 'base_template': base_template, }, context_instance=RequestContext(request)) def back_local(request): @@ -887,7 +995,7 @@ def back_local(request): return HttpResponseRedirect(redirect_url) -def group_admin(request): +def sys_group_admin(request): if not request.user.is_staff: raise Http404 @@ -898,9 +1006,10 @@ def group_admin(request): except ValueError: current_page = 1 per_page = 25 - + groups_plus_one = ccnet_rpc.get_all_groups(per_page * (current_page -1), per_page +1) + groups = groups_plus_one[:per_page] if len(groups_plus_one) == per_page + 1: @@ -908,11 +1017,90 @@ def group_admin(request): else: page_next = False - return render_to_response("group_admin.html", { - "groups": groups, + return render_to_response('sys_group_admin.html', { + 'groups': groups, '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)) + +def sys_org_admin(request): + if not request.user.is_staff: + raise Http404 + + orgs = ccnet_rpc.get_all_orgs(0, sys.maxint) + + return render_to_response('sys_org_admin.html', { + 'orgs': orgs, + }, context_instance=RequestContext(request)) + +def org_group_admin(request): + if not request.user.is_staff and not request.user.org.is_staff: + raise Http404 + + # 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', '25')) + except ValueError: + current_page = 1 + per_page = 25 + + groups_plus_one = ccnet_rpc.get_org_groups (request.user.org.org_id, + per_page * (current_page -1), + per_page +1) + + groups = groups_plus_one[:per_page] + + if len(groups_plus_one) == per_page + 1: + page_next = True + else: + page_next = False + + return render_to_response('org_group_admin.html', { + 'groups': groups, + '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)) + +def org_remove(request, org_id): + if not request.user.is_staff: + raise Http404 + + try: + org_id_int = int(org_id) + except ValueError: + return HttpResponseRedirect(reverse('sys_org_admin')) + + # Remove repos in that org + seafserv_threaded_rpc.remove_org_repo_by_org_id(org_id_int) + + # TODO: Remove repos in org's groups + + ccnet_rpc.remove_org(org_id_int) + + return HttpResponseRedirect(reverse('sys_org_admin')) + +@login_required +def org_info(request): + if not request.user.org: + raise Http404 + + org = request.user.org + + org_members = ccnet_rpc.get_org_emailusers(org.url_prefix, 0, sys.maxint) + for member in org_members: + member.short_username = member.email.split('@')[0] + + groups = ccnet_rpc.get_org_groups(org.org_id, 0, sys.maxint) + + return render_to_response('org_info.html', { + 'org': org, + 'org_users': org_members, + 'groups': groups, + }, context_instance=RequestContext(request))