src/cm/views/user.py
author Simon Descarpentries <sid@sopinspace.com>
Mon, 21 Oct 2013 16:37:07 +0200
changeset 553 bf26fb47a14c
parent 544 18ac07f35974
child 580 f634a302c45e
permissions -rw-r--r--
To allow scrolling in Safari mobile, we set the content of text_view_comments frame in a jQuery UI layout. So the automated scrolling operations in c_sync.js must be adjustable to the right part to scroll. Also, if a comment have to be shown outside of the current viewport, we scroll the correct part to that viewport and then set the comment top Y offset to juste what it needs to avoid the "Add comment" button after scrolling operation. If not in Safari mobile, we add an offset here to avoid comment to display under the "Add comment" button.

from django.forms.models import inlineformset_factory
from cm.models import *
from cm.message import *
from django.contrib.auth import authenticate
from django.contrib.auth import login as django_login  
from django.forms import ModelForm
from django.contrib.auth.models import User
from django.forms.formsets import formset_factory
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.forms.util import ErrorList
from django.shortcuts import get_object_or_404
from cm.activity import register_activity
from cm.views import get_text_by_keys_or_404
from cm.message import display_message
from cm.utils import get_among, get_int
from cm.models import ApplicationConfiguration
from django.views.generic.list_detail import object_list
from django.contrib.auth.decorators import login_required
from cm.views import get_keys_from_dict
from cm.security import has_global_perm, has_global_perm_or_perm_on_text
from cm.exception import UnauthorizedException
from cm.cm_settings import SHOW_EMAILS_IN_ADMIN
from tagging.models import Tag
import sys
import re

USER_PAGINATION = 10

@has_global_perm('can_manage_workspace')
def user_list(request):    
    display_suspended_users = get_int(request.GET, 'display', 0)
    tag_selected = request.GET.get('tag_selected', 0)
    paginate_by = get_int(request.GET, 'paginate', USER_PAGINATION)
    order_by = get_among(request.GET, 'order', ('user__username',
                                              'user__email',
                                              '-user__username',
                                              '-user__email',
                                              'role__name',
                                              '-role__name',
                                              'user__date_joined',
                                              '-user__date_joined',
                                              ),
                          'user__username')
    
    UserRole.objects.create_userroles_text(None)
    
    if request.method == 'POST':
        # bulk apply
        if 'apply' in request.POST and not 'save' in request.POST:
            action = request.POST.get('action', None)
            user_profile_keys = get_keys_from_dict(request.POST, 'check-').keys()
            if action == 'disable':
                for user_profile_key in user_profile_keys:
                    profile = UserProfile.objects.get(key=user_profile_key)
                    if profile != request.user.get_profile():
                        profile.is_suspended = True
                        profile.save()             
                display_message(request, _(u"%(count)i User's access suspended") % {'count':len(user_profile_keys)})

            if action == 'enable':
                for user_profile_key in user_profile_keys:
                    profile = UserProfile.objects.get(key=user_profile_key)
                    profile.is_suspended = False
                    profile.save()             
                display_message(request, _(u"%(count)i User's access enabled") % {'count':len(user_profile_keys)})
            
            ROLE_RE = re.compile('role_(\d*)')
            match = ROLE_RE.match(action)
              
            if match:
                role_id = match.group(1)
                for user_profile_key in user_profile_keys:
                    user_role = UserRole.objects.get(user__userprofile__key=user_profile_key, text=None)
                    user_role.role_id = role_id
                    user_role.save()
                display_message(request, _(u"%(count)i user(s) role modified") % {'count':len(user_profile_keys)})
                
            return HttpResponseRedirect(reverse('user'))
        
        if 'save' in request.POST:
            user_profile_keys_roles = get_keys_from_dict(request.POST, 'user-role-')
            count = 0
            for user_profile_key in user_profile_keys_roles:
                role_id = user_profile_keys_roles[user_profile_key]
                if not user_profile_key:
                    user_role = UserRole.objects.get(user=None, text=None)
                else:                    
                    user_role = UserRole.objects.get(user__userprofile__key=user_profile_key, text=None)
                if (role_id != u'' or user_role.role_id != None) and role_id != unicode(user_role.role_id):
                    if role_id:
                        user_role.role_id = int(role_id)
                    else:
                        user_role.role_id = None
                    user_role.save()
                    count += 1
            display_message(request, _(u"%(count)i user(s) role modified") % {'count':count})                
            return HttpResponseRedirect(reverse('user'))
    try:
        anon_role = UserRole.objects.get(user=None, text=None).role
    except UserRole.DoesNotExist:
        anon_role = None
        
    context = {
               'anon_role' : anon_role,
               'all_roles' : Role.objects.all(),
               'anon_roles' : Role.objects.filter(anon=True),
               'display_suspended_users' : display_suspended_users,
               'tag_list' : Tag.objects.usage_for_model(UserProfile),
               'tag_selected': tag_selected,
               'SHOW_EMAILS_IN_ADMIN': SHOW_EMAILS_IN_ADMIN,
               }
    
    query = UserRole.objects.select_related().filter(text=None).filter(~Q(user=None)).order_by(order_by)
    if not display_suspended_users:
        query = query.exclude(Q(user__userprofile__is_suspended=True) & Q(user__is_active=True))
    else:
        # trick to include userprofile table anyway (to filter by tags)
        query = query.filter(Q(user__userprofile__is_suspended=True) | Q(user__userprofile__is_suspended=False))

    if tag_selected:     
        tag_ids = Tag.objects.filter(name=tag_selected)
        if tag_ids:   
            content_type_id = ContentType.objects.get_for_model(UserProfile).pk
            query = query.extra(where=['tagging_taggeditem.object_id = cm_userprofile.id', 
                                       'tagging_taggeditem.content_type_id = %i' %content_type_id,
                                       'tagging_taggeditem.tag_id = %i' %tag_ids[0].id],
                                tables=['tagging_taggeditem'],
                                )

    return object_list(request, query,
                       template_name='site/user_list.html',
                       paginate_by=paginate_by,
                       extra_context=context,
                       )

class UserForm(ModelForm):
    email = forms.EmailField(label=ugettext_lazy(u'E-mail address'), required=True)
    
    class Meta:
        model = User
        fields = ('email', 'first_name', 'last_name')
        

    def clean_email(self):
        """
        Validates that the supplied email is new to the site
        """
        if 'email' in self.cleaned_data:
            email = self.cleaned_data['email']
            try:
                if self.instance:
                    user = User.objects.exclude(email__iexact=self.instance.email).get(email__iexact=email)
                else:
                    user = User.objects.get(email__iexact=email)
            except User.DoesNotExist:
                return email
            raise forms.ValidationError(_(u'This user is already a member.'))
        
class MassUserForm(forms.Form):
    email = forms.CharField(label=ugettext_lazy(u'Emails'),
                           help_text=ugettext_lazy(u'Add multiples emails one per line (or separated by "," or ";")'),
                           widget=forms.Textarea,
                           required=True)
    
class UserRoleForm(ModelForm):
    class Meta:
        model = UserRole
        fields = ('role',)

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=':',
                 empty_permitted=False, instance=None):
        ModelForm.__init__(self, data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, instance)

        # override manually
        role_field = self.fields['role']
        #role_field.required = True
        role_field.label = _(u'Workspace level role')
        role_field.help_text = _(u'This role will apply to every text in the workspace. To share only a (few) texts with this user, you can leave this blank and delegate roles on texts once the user is created.')
        self.fields['role'] = role_field
        
class UserRoleTextForm(ModelForm):
    class Meta:
        model = UserRole
        fields = ('role',)

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=':',
                 empty_permitted=False, instance=None):
        ModelForm.__init__(self, data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, instance)

        # override manually
        role_field = self.fields['role']
        #role_field.required = True
        role_field.label = _(u'Text level role')
        role_field.help_text = _(u'This role will apply only to this text.')
        self.fields['role'] = role_field
        
class UserProfileForm(ModelForm):
    class Meta:
        model = UserProfile
        fields = ('is_suspended', 'tags')

class MyUserProfileForm(ModelForm):
    class Meta:
        model = UserProfile
        fields = ('tags',)

class UserProfileAddForm(ModelForm):
    class Meta:
        model = UserProfile
        fields = ('tags',)
        
class UserProfileRegisterForm(ModelForm):
    class Meta:
        model = UserProfile

class UserAddForm(forms.Form):
    note = forms.CharField(label=ugettext_lazy(u'Note'),
                           help_text=ugettext_lazy(u'Optional text to add to invitation email'),
                           widget=forms.Textarea,
                           required=False)


SEPARATORS_RE = re.compile('[;,\n]+')

@has_global_perm_or_perm_on_text('can_manage_workspace', 'can_manage_text')
def user_mass_add(request, key=None):
    return user_add(request, key=key, mass=True)

@has_global_perm_or_perm_on_text('can_manage_workspace', 'can_manage_text')
def user_add(request, key=None, mass=False):
    text = get_text_by_keys_or_404(key) if key else None
    if request.method == 'POST':
        userform = UserForm(request.POST) if not mass else MassUserForm(request.POST)
        userroleform = UserRoleForm(request.POST) if not(key) else None
        noteform = UserAddForm(request.POST)
        userprofileform = UserProfileAddForm(request.POST)
        localroleform = UserRoleTextForm(request.POST, prefix="local") if key else None
        if userform.is_valid() and (not userroleform or userroleform.is_valid()) and noteform.is_valid() and userprofileform.is_valid() and (not localroleform or localroleform.is_valid()):
            data = userform.cleaned_data
            data.update(userprofileform.cleaned_data)
            data.update(noteform.cleaned_data)
            emails = data['email']
            del data['email']
            email_created = set()
            for email in [s.strip() for s in SEPARATORS_RE.split(emails)]:
                if email and not User.objects.filter(email__iexact=email) and email not in email_created:
                    user = UserProfile.objects.create_inactive_user(email, True, **data)
                    if key:
                        localuserrole = UserRole.objects.create(user=user, role=localroleform.cleaned_data['role'], text=text)
                    else:
                        userrole = UserRole.objects.create(user=user, role=userroleform.cleaned_data['role'], text=None)
                    email_created.add(email)
                    register_activity(request, "user_created", user=user)
            display_message(request, ungettext(u'%(nb_users)d user added', u'%(nb_users)d users added', len(email_created)) % {'nb_users': len(email_created)})
            if key:
                return HttpResponseRedirect(reverse('text-share', args=[text.key]))
            else:
                return HttpResponseRedirect(reverse('user'))
    else:
        userform = UserForm() if not mass else MassUserForm()
        userroleform = UserRoleForm() if not(key) else None
        userprofileform = UserProfileAddForm()
        noteform = UserAddForm()
        localroleform = UserRoleTextForm(prefix="local") if key else None
    
    if key:
        template = 'site/user_mass_add_text.html' if mass else 'site/user_add_text.html'
    else:
        template = 'site/user_mass_add.html' if mass else 'site/user_add.html'

    return render_to_response(template, {'forms' : [userform, userprofileform , userroleform, noteform, localroleform],
                                                               'save_name' : ungettext(u'Add user', u'Add users', 2 if mass else 1),
                                                               'mass' : mass,
                                                               'text' : text,
                                                                }, context_instance=RequestContext(request))

class UserValidateForm(ModelForm):
    email = forms.EmailField(label=ugettext_lazy(u'Email'), required=True)
    
    class Meta:
        model = User
        fields = ('email', 'username', 'first_name', 'last_name')

    def clean_username(self):
        """
        Validates that the supplied username is unique for the site.
        """
        if 'username' in self.cleaned_data:
            username = self.cleaned_data['username']
            try:
                user = User.objects.get(username__exact=username)
            except User.DoesNotExist:
                return username
            raise forms.ValidationError(_(u'This username is already in use. Please supply a different username.'))
        
from django.contrib.auth.forms import SetPasswordForm

def user_activate(request, key):
    try:
        profile = UserProfile.objects.get(adminkey=key)
        user = profile.user
        if not user.is_active:
            if request.method == 'POST':
                userform = UserValidateForm(request.POST, instance=user)
                pwform = SetPasswordForm(profile.user, request.POST)
                if userform.is_valid() and pwform.is_valid():
                    userform.save()
                    pwform.save()
                    user.is_active = True
                    user.save()
                    # login
                    user.backend = 'django.contrib.auth.backends.ModelBackend'
                    django_login(request, user)
                    register_activity(request, "user_activated", user=user)
                    display_message(request, _(u"Your account has been activated. You're now logged-in."))
                    
                    return HttpResponseRedirect(reverse('index'))
            else:
                user.username = ''
                userform = UserValidateForm(instance=user)
                pwform = SetPasswordForm(user)
            
            return render_to_response('site/activate.html', {
                                                                  'forms' : [userform, pwform],
                                                                  'title': _(u'Activate your account'),
                                                                  'save_name' : _(u'activate account'),
                                                                  }, context_instance=RequestContext(request))
        else:
            user.backend = 'django.contrib.auth.backends.ModelBackend'
            django_login(request, user)
            display_message(request, _(u"Your account has been activated. You're now logged-in."))
            
            return HttpResponseRedirect(reverse('index'))
                        
    except UserProfile.DoesNotExist:
        raise UnauthorizedException('No profile')

#@has_global_perm('can_manage_workspace')
#def user_delete(request, key):
#    try:
#        if request.method == 'POST':
#            profile = UserProfile.objects.get(key=key)
#            profile.delete()
#            display_message(request, "User %s has been deleted." %(profile.simple_print()))
#            return HttpResponse('') # no redirect because this is called by js
#    except UserProfile.DoesNotExist:
#        raise UnauthorizedException('No profile')

@has_global_perm('can_manage_workspace')
def user_suspend(request, key):
    if request.method == 'POST':
        profile = get_object_or_404(UserProfile, key=key)
        profile.is_suspended = True
        profile.save()
        if profile.user.is_active:            
            display_message(request, _(u"User's access %(prof)s has been suspended.") % {'prof':profile.simple_print()})
            register_activity(request, "user_suspended", user=profile.user)
        else:
            # make use active but disabled
            profile.user.is_active = True 
            profile.user.save()
            display_message(request, _(u"User's access %(prof)s has been refused.") % {'prof':profile.simple_print()})
            register_activity(request, "user_refused", user=profile.user)                
        return HttpResponse('') # no redirect because this is called by js
    raise UnauthorizedException('')
    
@has_global_perm('can_manage_workspace')
def user_enable(request, key):
    if request.method == 'POST':
        profile = get_object_or_404(UserProfile, key=key)
        profile.is_suspended = False
        profile.save()
        if profile.user.is_active:
            display_message(request, _(u"User's access %(prof)s has been restored.") % {'prof':profile.simple_print()})
            register_activity(request, "user_enabled", user=profile.user)
        else: # new member approval
            profile.send_activation_email()
            display_message(request, _(u"User's access %(prof)s has been approved.") % {'prof':profile.simple_print()})
            register_activity(request, "user_approved", user=profile.user)                
        return HttpResponse('') # no redirect because this is called by js
    raise UnauthorizedException('')
    
def user_send_invitation(request, key):
    if request.method == 'POST':
        profile = get_object_or_404(UserProfile, key=key)
        profile.send_invitation_email()
        
        display_message(request, _(u"A new invitation has been sent to user %(prof)s.") % {'prof':profile.simple_print()})
        return HttpResponse('') # no redirect because this is called by js
    raise UnauthorizedException('')

from django.contrib.auth.forms import PasswordChangeForm

@login_required()
def profile(request):
    user = request.user
    profile = user.get_profile()
    if request.method == 'POST':
        userform = UserForm(request.POST, instance=user)
        userprofileform = MyUserProfileForm(request.POST, instance=profile)
        
        if userform.is_valid() and userprofileform.is_valid():
            userform.save()
            userprofileform.save()
            display_message(request, _(u'Profile updated'))
            return HttpResponseRedirect(reverse('index'))
    else:
        userform = UserForm(instance=user)
        userprofileform = MyUserProfileForm(instance=profile)
    
    return render_to_response('site/profile.html', {'forms' : [userform, userprofileform],
                                                               'title' : 'Profile',
                                                                }, context_instance=RequestContext(request))

@login_required()
def profile_pw(request):
    user = request.user
    profile = user.get_profile()
    if request.method == 'POST':
        pwform = PasswordChangeForm(profile.user, data = request.POST)
        if pwform.is_valid():
            pwform.save()
            display_message(request, _(u'Password changed'))
            return HttpResponseRedirect(reverse('profile'))
    else:
        pwform = PasswordChangeForm(profile.user)
    return render_to_response('site/profile_pw.html', {'forms' : [pwform],
                                                               'title' : 'Password',
                                                                }, context_instance=RequestContext(request))

class AnonUserRoleForm(UserRoleForm):
    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=':',
                 empty_permitted=False, instance=None):
        ModelForm.__init__(self, data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, instance)

        # override manually
        role_field = self.fields['role']
        role_field.required = False
        role_field.choices = [(u'', u'---------')] + [(r.id, str(r)) for r in Role.objects.filter(anon=True)] # limit anon choices
        
        self.fields['role'] = role_field

@has_global_perm('can_manage_workspace')    
def user_anon_edit(request):
    userrole, created = UserRole.objects.get_or_create(user=None, text=None)
    if request.method == 'POST':
        userroleform = AnonUserRoleForm(request.POST, instance=userrole)
        if userroleform.is_valid():
            userroleform.save()
            display_message(request, _(u'Anonymous user role modified.'))
            return HttpResponseRedirect(reverse('user'))
    else:
        userroleform = AnonUserRoleForm(instance=userrole)
    
    return render_to_response('site/user_edit.html', {'form' : userroleform,
                                                               'title' : 'Edit anonymous user',
                                                                }, context_instance=RequestContext(request))

@has_global_perm('can_manage_workspace')    
def user_edit(request, key):
    profile = get_object_or_404(UserProfile, key=key)
    user = profile.user
    userrole = profile.global_userrole()
    if request.method == 'POST':
        userform = UserForm(request.POST, instance=user)
        userprofileform = UserProfileForm(request.POST, instance=profile)
        userroleform = UserRoleForm(request.POST, instance=userrole)
        if userform.is_valid() and userroleform.is_valid() and userprofileform.is_valid():
            userform.save()
            userroleform.save()
            userprofileform.save()
            display_message(request, _(u'User modified'))
            return HttpResponseRedirect(reverse('user'))
    else:
        userform = UserForm(instance=user)
        userprofileform = UserProfileForm(instance=profile)
        userroleform = UserRoleForm(instance=userrole)
    
    return render_to_response('site/user_edit.html', {'forms' : [userform , userprofileform, userroleform],
                                                               'title' : 'Edit user',
                                                               'user_edit' : user,
                                                                }, context_instance=RequestContext(request))

# user contact form (for logged-in users only

class UserContactForm(forms.Form):
    subject = forms.CharField(label=ugettext_lazy(u'Subject'),
                           help_text=ugettext_lazy(u'Subject of the email'),
                           required=True)

    body = forms.CharField(label=ugettext_lazy(u'Body'),
                           help_text=ugettext_lazy(u'Body of the email'),
                           widget=forms.Textarea,
                           required=True)

@login_required
def user_contact(request, key):
    recipient_profile = get_object_or_404(UserProfile, key=key)

    if request.method == 'POST':
        contact_form = UserContactForm(request.POST)
        if contact_form.is_valid():
            data = contact_form.cleaned_data
            message = render_to_string('email/user_contact_email.txt',
                                       { 
                                         'body' : data['body'],
                                         'CONF': ApplicationConfiguration
                                          })
        
            send_mail(data['subject'], message, request.user.email, [recipient_profile.user.email])
            
            display_message(request, _(u'Email sent.'))
            return HttpResponseRedirect(reverse('index'))
    else:
        contact_form = UserContactForm()
    
    return render_to_response('site/user_contact.html', {'form' : contact_form,
                                                         'save_name' : 'send',
                                                         'recipient_profile' : recipient_profile,
                                                                }, context_instance=RequestContext(request))


from django.contrib.auth.forms import AuthenticationForm

def cm_login(request, user):
    # make sure user has a profile
    try:
        user.get_profile()
    except UserProfile.DoesNotExist :
        UserProfile.objects.create(user=user)
        
    if user.get_profile().is_suspended:
        display_message(request, _(u"This account is suspended, contact the workspace administrator."))
        return HttpResponseRedirect(reverse('index'))
                
    user.backend = 'django.contrib.auth.backends.ModelBackend'
    django_login(request, user)
    
    display_message(request, _(u"You're logged in!"))
    next = request.POST.get('next', None)
    q = request.POST.get('q', None)
    if next and next.startswith('/'):
        if q:
            return HttpResponseRedirect(next + '?' + q)
        else:
            return HttpResponseRedirect(next)
    else:
        return HttpResponseRedirect(reverse('index'))

def login(request): 
    request.session.set_test_cookie()
    
    if request.method == 'POST':
        form = AuthenticationForm(request, request.POST)
        if form.is_valid():
            user = form.get_user()
            
            return cm_login(request, user)            
    else:    
        form = AuthenticationForm()        
    
    return render_to_response('site/login.html', {'form':form}, context_instance=RequestContext(request))

from django.contrib.auth import logout as django_logout

def logout(request):
    django_logout(request)
    display_message(request, _(u"You've been logged out."))
    return HttpResponseRedirect(reverse('index'))

def register(request):
    if request.method == 'POST':
        userform = UserForm(request.POST)
        userprofileaddform = UserProfileRegisterForm(request.POST)
        
        if userform.is_valid() and userprofileaddform.is_valid():
            data = userform.cleaned_data
            data.update(userprofileaddform.cleaned_data)
            user = UserProfile.objects.create_inactive_user(userform.cleaned_data['email'], False, **userprofileaddform.cleaned_data)
            profile = user.get_profile()
            if ApplicationConfiguration.get_key('workspace_registration_moderation', False): # need moderation
                profile.is_suspended = True
                profile.save()
                display_message(request, _(u"You've been registered, you will receive a confirmation mail once a moderator has approved your membership."))                
            else:
                profile.send_activation_email()
                display_message(request, _(u"You've been registered, please check your email for the confirm message."))                
            return HttpResponseRedirect(reverse('index'))
    else:    
        userform = UserForm()
        userprofileaddform = UserProfileRegisterForm()
    
    return render_to_response('site/register.html', {'forms':[userform, userprofileaddform]}, context_instance=RequestContext(request))