user.py 15.8 KB
Newer Older
baltery's avatar
baltery 已提交
1 2 3
# ~*~ coding: utf-8 ~*~

from __future__ import unicode_literals
X
xiaokong1937@gmail.com 已提交
4

baltery's avatar
baltery 已提交
5
import json
X
xiaokong1937@gmail.com 已提交
6
import uuid
baltery's avatar
baltery 已提交
7 8 9
import csv
import codecs
from io import StringIO
X
xiaokong1937@gmail.com 已提交
10 11 12

from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
baltery's avatar
baltery 已提交
13 14
from django.core.cache import cache
from django.http import HttpResponse, JsonResponse
X
xiaokong1937@gmail.com 已提交
15
from django.shortcuts import redirect
baltery's avatar
baltery 已提交
16 17 18 19 20 21 22
from django.urls import reverse_lazy, reverse
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import ListView
from django.views.generic.base import TemplateView
baltery's avatar
baltery 已提交
23 24 25
from django.views.generic.edit import (
    CreateView, UpdateView, FormMixin, FormView
)
baltery's avatar
baltery 已提交
26 27
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.decorators.csrf import csrf_exempt
baltery's avatar
baltery 已提交
28
from django.contrib.auth import logout as auth_logout
baltery's avatar
baltery 已提交
29

X
xiaokong1937@gmail.com 已提交
30 31
from .. import forms
from ..models import User, UserGroup
baltery's avatar
baltery 已提交
32 33
from ..utils import AdminUserRequiredMixin, send_user_created_mail
from ..signals import on_user_created
baltery's avatar
baltery 已提交
34
from common.mixins import JSONResponseMixin
baltery's avatar
baltery 已提交
35
from common.utils import get_logger, get_object_or_none
baltery's avatar
baltery 已提交
36 37
from perms.models import AssetPermission

baltery's avatar
baltery 已提交
38 39 40 41 42 43 44 45
__all__ = [
    'UserListView', 'UserCreateView', 'UserDetailView',
    'UserUpdateView', 'UserAssetPermissionCreateView',
    'UserAssetPermissionView', 'UserGrantedAssetView',
    'UserExportView',  'UserBulkImportView', 'UserProfileView',
    'UserProfileUpdateView', 'UserPasswordUpdateView',
    'UserPublicKeyUpdateView', 'UserBulkUpdateView',
]
X
xiaokong1937@gmail.com 已提交
46

baltery's avatar
baltery 已提交
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
logger = get_logger(__name__)


class UserListView(AdminUserRequiredMixin, TemplateView):
    template_name = 'users/user_list.html'

    def get_context_data(self, **kwargs):
        context = super(UserListView, self).get_context_data(**kwargs)
        context.update({
            'app': _('Users'),
            'action': _('User list'),
        })
        return context


class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
    model = User
    form_class = forms.UserCreateUpdateForm
    template_name = 'users/user_create.html'
    success_url = reverse_lazy('users:user-list')
baltery's avatar
baltery 已提交
67
    success_message = _('Create user <a href="{url}">{name}</a> successfully.')
baltery's avatar
baltery 已提交
68 69 70 71 72 73 74 75 76 77

    def get_context_data(self, **kwargs):
        context = super(UserCreateView, self).get_context_data(**kwargs)
        context.update({'app': _('Users'), 'action': _('Create user')})
        return context

    def form_valid(self, form):
        user = form.save(commit=False)
        user.created_by = self.request.user.username or 'System'
        user.save()
baltery's avatar
baltery 已提交
78
        on_user_created.send(self.__class__, user=user)
baltery's avatar
baltery 已提交
79 80 81
        return super(UserCreateView, self).form_valid(form)

    def get_success_message(self, cleaned_data):
baltery's avatar
baltery 已提交
82 83 84
        url = reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk})
        return self.success_message.format(
            url=url, name=self.object.name
baltery's avatar
baltery 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
        )


class UserUpdateView(AdminUserRequiredMixin, UpdateView):
    model = User
    form_class = forms.UserCreateUpdateForm
    template_name = 'users/user_update.html'
    context_object_name = 'user_object'
    success_url = reverse_lazy('users:user-list')

    def form_valid(self, form):
        username = self.object.username
        user = form.save(commit=False)
        user.username = username
        user.save()
        password = self.request.POST.get('password', '')
        if password:
            user.set_password(password)
        return super(UserUpdateView, self).form_valid(form)

    def get_context_data(self, **kwargs):
        context = super(UserUpdateView, self).get_context_data(**kwargs)
        context.update({'app': _('Users'), 'action': _('Update user')})
        return context


baltery's avatar
baltery 已提交
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
class UserBulkUpdateView(AdminUserRequiredMixin, ListView):
    model = User
    form_class = forms.UserBulkUpdateForm
    template_name = 'users/user_bulk_update.html'
    success_url = reverse_lazy('users:user-list')

    def get(self, request, *args, **kwargs):
        users_id = self.request.GET.get('users_id', '')
        self.id_list = [int(i) for i in users_id.split(',') if i.isdigit()]

        if kwargs.get('form'):
            self.form = kwargs['form']
        elif users_id:
            self.form = self.form_class(
                initial={'users': self.id_list}
            )
        else:
            self.form = self.form_class()
        return super(UserBulkUpdateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            form.save()
            return redirect(self.success_url)
        else:
            return self.get(request, form=form, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = {
            'app': 'Assets',
            'action': 'Bulk update asset',
            'form': self.form,
            'users_selected': self.id_list,
            'users': User.objects.all(),
        }
        kwargs.update(context)
        return super(UserBulkUpdateView, self).get_context_data(**kwargs)


baltery's avatar
baltery 已提交
151 152 153
class UserDetailView(AdminUserRequiredMixin, DetailView):
    model = User
    template_name = 'users/user_detail.html'
154
    context_object_name = "user_object"
baltery's avatar
baltery 已提交
155 156 157 158 159 160 161 162 163 164 165 166

    def get_context_data(self, **kwargs):
        groups = UserGroup.objects.exclude(id__in=self.object.groups.all())
        context = {
            'app': _('Users'),
            'action': _('User detail'),
            'groups': groups
        }
        kwargs.update(context)
        return super(UserDetailView, self).get_context_data(**kwargs)


baltery's avatar
baltery 已提交
167 168 169 170 171 172 173 174 175 176
# USER_ATTR_MAPPING = (
#     ('name', 'Name'),
#     ('username', 'Username'),
#     ('email', 'Email'),
#     ('groups', 'User groups'),
#     ('role', 'Role'),
#     ('phone', 'Phone'),
#     ('wechat', 'Wechat'),
#     ('comment', 'Comment'),
# )
baltery's avatar
baltery 已提交
177 178


baltery's avatar
baltery 已提交
179 180
@method_decorator(csrf_exempt, name='dispatch')
class UserExportView(View):
baltery's avatar
baltery 已提交
181
    def get(self, request):
baltery's avatar
baltery 已提交
182 183 184 185 186 187
        fields = [
            User._meta.get_field(name)
            for name in [
                'id', 'name', 'username', 'email', 'role', 'wechat', 'phone',
                'enable_otp', 'is_active', 'comment',
            ]
baltery's avatar
baltery 已提交
188
        ]
baltery's avatar
baltery 已提交
189
        spm = request.GET.get('spm', '')
baltery's avatar
baltery 已提交
190 191 192 193 194 195
        users_id = cache.get(spm, ['1'])
        filename = 'users-{}.csv'.format(
            timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="%s"' % filename
        response.write(codecs.BOM_UTF8)
baltery's avatar
baltery 已提交
196
        users = User.objects.filter(id__in=users_id)
baltery's avatar
baltery 已提交
197 198
        writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)

baltery's avatar
baltery 已提交
199 200
        header = [field.verbose_name for field in fields]
        header.append(_('User groups'))
baltery's avatar
baltery 已提交
201
        writer.writerow(header)
baltery's avatar
baltery 已提交
202 203

        for user in users:
baltery's avatar
baltery 已提交
204
            groups = ','.join([group.name for group in user.groups.all()])
baltery's avatar
baltery 已提交
205 206 207
            data = [getattr(user, field.name) for field in fields]
            data.append(groups)
            writer.writerow(data)
baltery's avatar
baltery 已提交
208 209 210

        return response

baltery's avatar
baltery 已提交
211
    def post(self, request):
baltery's avatar
baltery 已提交
212 213 214 215
        try:
            users_id = json.loads(request.body).get('users_id', [])
        except ValueError:
            return HttpResponse('Json object not valid', status=400)
baltery's avatar
baltery 已提交
216
        spm = uuid.uuid4().hex
baltery's avatar
baltery 已提交
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
        cache.set(spm, users_id, 300)
        url = reverse('users:user-export') + '?spm=%s' % spm
        return JsonResponse({'redirect': url})


class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
    form_class = forms.FileForm

    def form_invalid(self, form):
        try:
            error = form.errors.values()[-1][-1]
        except Exception as e:
            error = _('Invalid file.')
        data = {
            'success': False,
            'msg': error
        }
        return self.render_json_response(data)

    def form_valid(self, form):
baltery's avatar
baltery 已提交
237 238 239 240 241 242
        file = form.cleaned_data['file']
        data = file.read().decode('utf-8').strip(codecs.BOM_UTF8.decode('utf-8'))
        csv_file = StringIO(data)
        reader = csv.reader(csv_file)
        csv_data = [row for row in reader]
        header_ = csv_data[0]
baltery's avatar
baltery 已提交
243 244 245 246 247 248 249 250 251 252 253
        fields = [
            User._meta.get_field(name)
            for name in [
                'id', 'name', 'username', 'email', 'role', 'wechat', 'phone',
                'enable_otp', 'is_active', 'comment',
            ]
        ]
        mapping_reverse = {field.verbose_name: field.name for field in fields}
        mapping_reverse[_('User groups')] = 'groups'
        attr = [mapping_reverse.get(n, None) for n in header_]
        if None in attr:
baltery's avatar
baltery 已提交
254 255 256
            data = {'valid': False,
                    'msg': 'Must be same format as '
                           'template or export file'}
baltery's avatar
baltery 已提交
257 258
            return self.render_json_response(data)

baltery's avatar
baltery 已提交
259 260 261 262
        created, updated, failed = [], [], []
        for row in csv_data[1:]:
            if set(row) == {''}:
                continue
baltery's avatar
baltery 已提交
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
            user_dict = dict(zip(attr, row))
            id_ = user_dict.pop('id', 0)
            user = get_object_or_none(User, id=id_)
            for k, v in user_dict.items():
                if k in ['enable_otp', 'is_active']:
                    if v.lower() == 'false':
                        v = False
                    else:
                        v = bool(v)
                elif k == 'groups':
                    groups_name = v.split(',')
                    v = UserGroup.objects.filter(name__in=groups_name)
                else:
                    continue
                user_dict[k] = v

baltery's avatar
baltery 已提交
279 280
            if not user:
                try:
baltery's avatar
baltery 已提交
281
                    groups = user_dict.pop('groups')
baltery's avatar
baltery 已提交
282
                    user = User.objects.create(**user_dict)
baltery's avatar
baltery 已提交
283
                    user.groups.set(groups)
baltery's avatar
baltery 已提交
284
                    created.append(user_dict['username'])
baltery's avatar
baltery 已提交
285
                    on_user_created.send(self.__class__, user=user)
baltery's avatar
baltery 已提交
286 287 288 289
                except Exception as e:
                    failed.append('%s: %s' % (user_dict['username'], str(e)))
            else:
                for k, v in user_dict.items():
baltery's avatar
baltery 已提交
290 291 292
                    if k == 'groups':
                        user.groups.set(v)
                        continue
baltery's avatar
baltery 已提交
293 294 295 296 297 298 299
                    if v:
                        setattr(user, k, v)
                try:
                    user.save()
                    updated.append(user_dict['username'])
                except Exception as e:
                    failed.append('%s: %s' % (user_dict['username'], str(e)))
baltery's avatar
baltery 已提交
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378

        data = {
            'created': created,
            'created_info': 'Created {}'.format(len(created)),
            'updated': updated,
            'updated_info': 'Updated {}'.format(len(updated)),
            'failed': failed,
            'failed_info': 'Failed {}'.format(len(failed)),
            'valid': True,
            'msg': 'Created: {}. Updated: {}, Error: {}'.format(
                len(created), len(updated), len(failed))
        }
        return self.render_json_response(data)


class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin,
                              SingleObjectMixin, ListView):
    model = User
    template_name = 'users/user_asset_permission.html'
    context_object_name = 'user'
    form_class = forms.UserPrivateAssetPermissionForm

    def get(self, request, *args, **kwargs):
        self.object = self.get_object(queryset=User.objects.all())
        return super(UserAssetPermissionView, self).get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = {
            'app': 'Users',
            'action': 'User asset permissions',
        }
        kwargs.update(context)
        return super(UserAssetPermissionView, self).get_context_data(**kwargs)


class UserAssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
    form_class = forms.UserPrivateAssetPermissionForm
    model = AssetPermission

    def get(self, request, *args, **kwargs):
        user = self.get_object(queryset=User.objects.all())
        return redirect(reverse('users:user-asset-permission',
                                kwargs={'pk': user.id}))

    def post(self, request, *args, **kwargs):
        self.user = self.get_object(queryset=User.objects.all())
        return super(UserAssetPermissionCreateView, self)\
            .post(request, *args, **kwargs)

    def get_form(self, form_class=None):
        form = super(UserAssetPermissionCreateView, self)\
            .get_form(form_class=form_class)
        form.user = self.user
        return form

    def form_invalid(self, form):
        return redirect(reverse('users:user-asset-permission',
                                kwargs={'pk': self.user.id}))

    def get_success_url(self):
        return reverse('users:user-asset-permission',
                       kwargs={'pk': self.user.id})


class UserGrantedAssetView(AdminUserRequiredMixin, DetailView):
    model = User
    template_name = 'users/user_granted_asset.html'
    context_object_name = 'user'

    def get(self, request, *args, **kwargs):
        self.object = self.get_object(queryset=User.objects.all())
        return super(UserGrantedAssetView, self).get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = {
            'app': 'User',
            'action': 'User granted asset',
        }
        kwargs.update(context)
X
xiaokong1937@gmail.com 已提交
379 380 381 382 383 384 385
        return super(UserGrantedAssetView, self).get_context_data(**kwargs)


class UserProfileView(LoginRequiredMixin, TemplateView):
    template_name = 'users/user_profile.html'

    def get_context_data(self, **kwargs):
baltery's avatar
baltery 已提交
386
        from perms.utils import get_user_granted_assets
X
xiaokong1937@gmail.com 已提交
387 388 389 390 391 392 393 394
        assets = get_user_granted_assets(self.request.user)
        context = {
            'app': 'User',
            'action': 'User Profile',
            'assets': assets,
        }
        kwargs.update(context)
        return super(UserProfileView, self).get_context_data(**kwargs)
baltery's avatar
baltery 已提交
395 396 397 398 399 400 401


class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
    template_name = 'users/user_profile_update.html'
    model = User
    form_class = forms.UserProfileForm
    success_url = reverse_lazy('users:user-profile')
baltery's avatar
baltery 已提交
402
    success_message = _('Create user <a href="{url}">{name}</a> successfully.')
baltery's avatar
baltery 已提交
403 404 405 406 407

    def get_object(self, queryset=None):
        return self.request.user

    def get_success_message(self, cleaned_data):
baltery's avatar
baltery 已提交
408 409 410
        url = reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk})
        return self.success_message.format(
            url=url, name=self.object.name
baltery's avatar
baltery 已提交
411 412 413 414 415
        )

    def get_context_data(self, **kwargs):
        context = {
            'app': 'User',
baltery's avatar
baltery 已提交
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
            'action': 'Profile update',
        }
        kwargs.update(context)
        return super(UserProfileUpdateView, self).get_context_data(**kwargs)


class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
    template_name = 'users/user_password_update.html'
    model = User
    form_class = forms.UserPasswordForm
    success_url = reverse_lazy('users:user-profile')

    def get_object(self, queryset=None):
        return self.request.user

    def get_context_data(self, **kwargs):
        context = {
            'app': 'User',
            'action': 'Password update',
        }
        kwargs.update(context)
        return super(UserPasswordUpdateView, self).get_context_data(**kwargs)

    def get_success_url(self):
        auth_logout(self.request)
        return super(UserPasswordUpdateView, self).get_success_url()


class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
    template_name = 'users/user_pubkey_update.html'
    model = User
    form_class = forms.UserPublicKeyForm
    success_url = reverse_lazy('users:user-profile')

    def get_object(self, queryset=None):
        return self.request.user

    def get_context_data(self, **kwargs):
        context = {
            'app': 'User',
            'action': 'Public key update',
baltery's avatar
baltery 已提交
457 458
        }
        kwargs.update(context)
baltery's avatar
baltery 已提交
459
        return super(UserPublicKeyUpdateView, self).get_context_data(**kwargs)