# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
import json
import uuid
import csv
import codecs
from io import StringIO
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.core.cache import cache
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect
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
from django.views.generic.edit import (
CreateView, UpdateView, FormMixin, FormView
)
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth import logout as auth_logout
from .. import forms
from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin, user_add_success_next
from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none
from perms.models import AssetPermission
__all__ = [
'UserListView', 'UserCreateView', 'UserDetailView',
'UserUpdateView', 'UserAssetPermissionCreateView',
'UserAssetPermissionView', 'UserGrantedAssetView',
'UserExportView', 'UserBulkImportView', 'UserProfileView',
'UserProfileUpdateView', 'UserPasswordUpdateView',
'UserPublicKeyUpdateView', 'UserBulkUpdateView',
]
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')
success_message = _('Create user {name} successfully.')
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):
url = reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk})
return self.success_message.format(
url=url, name=self.object.name
)
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
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)
class UserDetailView(AdminUserRequiredMixin, DetailView):
model = User
template_name = 'users/user_detail.html'
context_object_name = "user_object"
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)
# USER_ATTR_MAPPING = (
# ('name', 'Name'),
# ('username', 'Username'),
# ('email', 'Email'),
# ('groups', 'User groups'),
# ('role', 'Role'),
# ('phone', 'Phone'),
# ('wechat', 'Wechat'),
# ('comment', 'Comment'),
# )
@method_decorator(csrf_exempt, name='dispatch')
class UserExportView(View):
def get(self, request):
fields = [
User._meta.get_field(name)
for name in [
'id', 'name', 'username', 'email', 'role', 'wechat', 'phone',
'enable_otp', 'is_active', 'comment',
]
]
spm = request.GET.get('spm', '')
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)
users = User.objects.filter(id__in=users_id)
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
header = [field.verbose_name for field in fields]
header.append(_('User groups'))
writer.writerow(header)
for user in users:
groups = ','.join([group.name for group in user.groups.all()])
data = [getattr(user, field.name) for field in fields]
data.append(groups)
writer.writerow(data)
return response
def post(self, request):
try:
users_id = json.loads(request.body).get('users_id', [])
except ValueError:
return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().hex
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):
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]
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:
data = {'valid': False,
'msg': 'Must be same format as '
'template or export file'}
return self.render_json_response(data)
created, updated, failed = [], [], []
for row in csv_data[1:]:
if set(row) == {''}:
continue
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
if not user:
try:
groups = user_dict.pop('groups')
user = User.objects.create(**user_dict)
user.groups.set(groups)
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():
if k == 'groups':
user.groups.set(v)
continue
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)))
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)
return super(UserGrantedAssetView, self).get_context_data(**kwargs)
class UserProfileView(LoginRequiredMixin, TemplateView):
template_name = 'users/user_profile.html'
def get_context_data(self, **kwargs):
from perms.utils import get_user_granted_assets
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)
class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
template_name = 'users/user_profile_update.html'
model = User
form_class = forms.UserProfileForm
success_url = reverse_lazy('users:user-profile')
success_message = _('Create user {name} successfully.')
def get_object(self, queryset=None):
return self.request.user
def get_success_message(self, cleaned_data):
url = reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk})
return self.success_message.format(
url=url, name=self.object.name
)
def get_context_data(self, **kwargs):
context = {
'app': 'User',
'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',
}
kwargs.update(context)
return super(UserPublicKeyUpdateView, self).get_context_data(**kwargs)