user.py 15.9 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
X
xiaokong1937@gmail.com 已提交
23 24
from django.views.generic.edit import (CreateView, UpdateView, FormMixin,
                                       FormView)
baltery's avatar
baltery 已提交
25 26
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.decorators.csrf import csrf_exempt
baltery's avatar
baltery 已提交
27
from django.contrib.auth import logout as auth_logout
baltery's avatar
baltery 已提交
28

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

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

baltery's avatar
baltery 已提交
44 45 46 47 48 49 50 51 52 53 54
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'),
baltery's avatar
baltery 已提交
55 56
            'groups': UserGroup.objects.all(),
            'form': forms.UserBulkUpdateForm(),
baltery's avatar
baltery 已提交
57 58 59 60 61 62 63 64 65
        })
        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 已提交
66
    success_message = _('Create user <a href="{url}">{name}</a> successfully.')
baltery's avatar
baltery 已提交
67 68 69 70 71 72 73 74 75 76 77 78 79 80

    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()
        user_add_success_next(user)
        return super(UserCreateView, self).form_valid(form)

    def get_success_message(self, cleaned_data):
baltery's avatar
baltery 已提交
81 82 83
        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 已提交
84 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
        )


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 已提交
110 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
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 已提交
150 151 152
class UserDetailView(AdminUserRequiredMixin, DetailView):
    model = User
    template_name = 'users/user_detail.html'
153
    context_object_name = "user_object"
baltery's avatar
baltery 已提交
154 155 156 157 158 159 160 161 162 163 164 165

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


baltery's avatar
baltery 已提交
178 179
@method_decorator(csrf_exempt, name='dispatch')
class UserExportView(View):
baltery's avatar
baltery 已提交
180
    def get(self, request):
baltery's avatar
baltery 已提交
181 182 183 184 185 186
        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 已提交
187
        ]
baltery's avatar
baltery 已提交
188
        spm = request.GET.get('spm', '')
baltery's avatar
baltery 已提交
189 190 191 192 193 194
        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 已提交
195
        users = User.objects.filter(id__in=users_id)
baltery's avatar
baltery 已提交
196 197
        writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)

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

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

        return response

baltery's avatar
baltery 已提交
210
    def post(self, request):
baltery's avatar
baltery 已提交
211 212 213 214
        try:
            users_id = json.loads(request.body).get('users_id', [])
        except ValueError:
            return HttpResponse('Json object not valid', status=400)
baltery's avatar
baltery 已提交
215
        spm = uuid.uuid4().hex
baltery's avatar
baltery 已提交
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
        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 已提交
236 237 238 239 240 241
        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 已提交
242 243 244 245 246 247 248 249 250 251 252
        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 已提交
253 254 255
            data = {'valid': False,
                    'msg': 'Must be same format as '
                           'template or export file'}
baltery's avatar
baltery 已提交
256 257
            return self.render_json_response(data)

baltery's avatar
baltery 已提交
258 259 260 261
        created, updated, failed = [], [], []
        for row in csv_data[1:]:
            if set(row) == {''}:
                continue
baltery's avatar
baltery 已提交
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
            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 已提交
278 279
            if not user:
                try:
baltery's avatar
baltery 已提交
280
                    groups = user_dict.pop('groups')
baltery's avatar
baltery 已提交
281
                    user = User.objects.create(**user_dict)
baltery's avatar
baltery 已提交
282
                    user.groups.set(groups)
baltery's avatar
baltery 已提交
283 284 285 286 287 288
                    created.append(user_dict['username'])
                    user_add_success_next(user)
                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 已提交
289 290 291
                    if k == 'groups':
                        user.groups.set(v)
                        continue
baltery's avatar
baltery 已提交
292 293 294 295 296 297 298
                    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 已提交
299 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

        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 已提交
378 379 380 381 382 383 384
        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 已提交
385
        from perms.utils import get_user_granted_assets
X
xiaokong1937@gmail.com 已提交
386 387 388 389 390 391 392 393
        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 已提交
394 395 396 397 398 399 400


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 已提交
401
    success_message = _('Create user <a href="{url}">{name}</a> successfully.')
baltery's avatar
baltery 已提交
402 403 404 405 406

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

    def get_success_message(self, cleaned_data):
baltery's avatar
baltery 已提交
407 408 409
        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 已提交
410 411 412 413 414
        )

    def get_context_data(self, **kwargs):
        context = {
            'app': 'User',
baltery's avatar
baltery 已提交
415 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
            '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 已提交
456 457
        }
        kwargs.update(context)
baltery's avatar
baltery 已提交
458
        return super(UserPublicKeyUpdateView, self).get_context_data(**kwargs)