From e4e6f59589d38b998f256fec4f42cf0a9fb71d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AB=E5=8D=83=E6=B5=81?= <40739051+jym503558564@users.noreply.github.com> Date: Fri, 22 Mar 2019 15:17:22 +0800 Subject: [PATCH] Export login log (#2511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Add]增加登录日志导出功能 * [Update]优化导出登录日志代码 * [Update]优化导出登录日志代码 * [Update]更改导出登录日志按钮 --- apps/audits/models.py | 18 +++ .../templates/audits/login_log_list.html | 106 ++++++++++++------ apps/audits/urls/view_urls.py | 1 + apps/audits/utils.py | 22 ++++ apps/audits/views.py | 50 +++++++++ 5 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 apps/audits/utils.py diff --git a/apps/audits/models.py b/apps/audits/models.py index 7e2302bb4..04177d6b4 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -1,6 +1,7 @@ import uuid from django.db import models +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -100,5 +101,22 @@ class UserLoginLog(models.Model): status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status')) datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login')) + @classmethod + def get_login_logs(cls, date_form=None, date_to=None, user=None, keyword=None): + login_logs = cls.objects.all() + if date_form and date_to: + login_logs = login_logs.filter( + datetime__gt=date_form, datetime__lt=date_to + ) + if user: + login_logs = login_logs.filter(username=user) + if keyword: + login_logs = login_logs.filter( + Q(ip__contains=keyword) | + Q(city__contains=keyword) | + Q(username__contains=keyword) + ) + return login_logs + class Meta: ordering = ['-datetime', 'username'] diff --git a/apps/audits/templates/audits/login_log_list.html b/apps/audits/templates/audits/login_log_list.html index 8b50d4d9a..53cb05560 100644 --- a/apps/audits/templates/audits/login_log_list.html +++ b/apps/audits/templates/audits/login_log_list.html @@ -17,10 +17,10 @@
- + {# #} to - +
@@ -32,7 +32,7 @@
- +
@@ -43,38 +43,57 @@
{% endblock %} +{% block table_container %} + + + + + + + + + + + + + + + + + + {% for login_log in object_list %} + + + + + + + + + + + + + {% endfor %} + +
{% trans 'ID' %}{% trans 'Username' %}{% trans 'Type' %}{% trans 'UA' %}{% trans 'IP' %}{% trans 'City' %}{% trans 'MFA' %}{% trans 'Reason' %}{% trans 'Status' %}{% trans 'Date' %}
{{ forloop.counter }}{{ login_log.username }}{{ login_log.get_type_display }} + {{ login_log.user_agent | truncatechars:20 }} + {{ login_log.ip }}{{ login_log.city }}{{ login_log.get_mfa_display }}{{ login_log.get_reason_display }}{{ login_log.get_status_display }}{{ login_log.datetime }}
+
+
+ +
+ +
+
+
-{% block table_head %} - {% trans 'ID' %} - {% trans 'Username' %} - {% trans 'Type' %} - {% trans 'UA' %} - {% trans 'IP' %} - {% trans 'City' %} - {% trans 'MFA' %} - {% trans 'Reason' %} - {% trans 'Status' %} - {% trans 'Date' %} {% endblock %} -{% block table_body %} - {% for login_log in object_list %} - - {{ forloop.counter }} - {{ login_log.username }} - {{ login_log.get_type_display }} - - {{ login_log.user_agent | truncatechars:20 }} - - {{ login_log.ip }} - {{ login_log.city }} - {{ login_log.get_mfa_display }} - {{ login_log.get_reason_display }} - {{ login_log.get_status_display }} - {{ login_log.datetime }} - - {% endfor %} -{% endblock %} {% block custom_foot_js %} @@ -95,6 +114,29 @@ width: 'auto' }); }) + .on('click', '.btn_export', function () { + var date_form = $('#id_date_from').val(); + var date_to = $('#id_date_to').val(); + var user = $('.select2 option:selected').val(); + var keyword = $('#search').val(); + $.ajax({ + url: "{% url "audits:login-log-export" %}", + method: 'POST', + data: JSON.stringify({ + 'date_form':date_form, + 'date_to':date_to, + 'user':user, + 'keyword':keyword + }), + dataType: "json", + success: function (data, textStatus) { + window.open(data.redirect) + }, + error: function () { + toastr.error('Export failed'); + } + }) + }) {% endblock %} diff --git a/apps/audits/urls/view_urls.py b/apps/audits/urls/view_urls.py index 473a2d83f..ef400cb99 100644 --- a/apps/audits/urls/view_urls.py +++ b/apps/audits/urls/view_urls.py @@ -14,4 +14,5 @@ urlpatterns = [ path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'), path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'), path('command-execution-log/', views.CommandExecutionListView.as_view(), name='command-execution-log-list'), + path('login-log/export/', views.LoginLogExportView.as_view(), name='login-log-export'), ] diff --git a/apps/audits/utils.py b/apps/audits/utils.py new file mode 100644 index 000000000..36c54e81b --- /dev/null +++ b/apps/audits/utils.py @@ -0,0 +1,22 @@ +import csv +import codecs +from django.http import HttpResponse + + +def get_excel_response(filename): + excel_response = HttpResponse(content_type='text/csv') + excel_response[ + 'Content-Disposition'] = 'attachment; filename="%s"' % filename + excel_response.write(codecs.BOM_UTF8) + return excel_response + + +def write_content_to_excel(response, header=None, login_logs=None, fields=None): + writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL) + if header: + writer.writerow(header) + if login_logs: + for log in login_logs: + data = [getattr(log, field.name) for field in fields] + writer.writerow(data) + return response \ No newline at end of file diff --git a/apps/audits/views.py b/apps/audits/views.py index a862ca066..3e2cc3473 100644 --- a/apps/audits/views.py +++ b/apps/audits/views.py @@ -1,8 +1,23 @@ +import csv +import json +import uuid +import codecs + + from django.conf import settings +from django.urls import reverse +from django.utils import timezone +from django.core.cache import cache +from django.http import HttpResponse, JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from django.views.decorators.csrf import csrf_exempt from django.views.generic import ListView from django.utils.translation import ugettext as _ +from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q +from audits.utils import get_excel_response, write_content_to_excel from common.mixins import DatetimeSearchMixin from common.permissions import AdminUserRequiredMixin @@ -232,3 +247,38 @@ class CommandExecutionListView(UserCommandExecutionListView): } kwargs.update(context) return super().get_context_data(**kwargs) + + +@method_decorator(csrf_exempt, name='dispatch') +class LoginLogExportView(LoginRequiredMixin, View): + + def get(self, request): + fields = [ + field for field in UserLoginLog._meta.fields + ] + filename = 'login-logs-{}.csv'.format( + timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S') + ) + excel_response = get_excel_response(filename) + header = [field.verbose_name for field in fields] + login_logs = cache.get(request.GET.get('spm', ''), []) + + response = write_content_to_excel(excel_response, login_logs=login_logs, + header=header, fields=fields) + return response + + def post(self, request): + try: + date_form = json.loads(request.body).get('date_form', []) + date_to = json.loads(request.body).get('date_to', []) + user = json.loads(request.body).get('user', []) + keyword = json.loads(request.body).get('keyword', []) + + login_logs = UserLoginLog.get_login_logs( + date_form=date_form, date_to=date_to, user=user, keyword=keyword) + except ValueError: + return HttpResponse('Json object not valid', status=400) + spm = uuid.uuid4().hex + cache.set(spm, login_logs, 300) + url = reverse('audits:login-log-export') + '?spm=%s' % spm + return JsonResponse({'redirect': url}) \ No newline at end of file