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 @@
@@ -32,7 +32,7 @@
-
+
@@ -43,38 +43,57 @@
{% endblock %}
+{% block table_container %}
+
+
+
+ {% trans 'ID' %} |
+ {% trans 'Username' %} |
+ {% trans 'Type' %} |
+ {% trans 'UA' %} |
+ {% trans 'IP' %} |
+ {% trans 'City' %} |
+ {% trans 'MFA' %} |
+ {% trans 'Reason' %} |
+ {% trans 'Status' %} |
+ {% trans 'Date' %} |
+
+
+
+
+ {% 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 %}
+
+
+
-{% 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