| @@ -20,7 +20,7 @@ from account.models import UserInfo | ||
| 20 | 20 | from api.encrypt_views import get_ciphertext | 
| 21 | 21 | from coupon.models import CouponInfo, UserCouponInfo | 
| 22 | 22 | from integral.models import SaleclerkSubmitLogInfo | 
| 23 | -from logs.models import ComplementCodeLogInfo, MchInfoEncryptLogInfo | |
| 23 | +from logs.models import ComplementCodeLogInfo, MchInfoEncryptLogInfo, AdministratorLoginLogInfo | |
| 24 | 24 | from mch.models import AdministratorInfo, BrandInfo, ConsumeInfoSubmitLogInfo, DistributorInfo, ModelInfo | 
| 25 | 25 | from member.models import (GoodsInfo, GoodsOrderInfo, MemberActivityGroupShareInfo, MemberActivityInfo, | 
| 26 | 26 | MemberActivitySignupInfo) | 
| @@ -1709,7 +1709,7 @@ def administrator_update(request): | ||
| 1709 | 1709 |      admin_id = request.POST.get('admin_id', '') | 
| 1710 | 1710 |  | 
| 1711 | 1711 |      target_admin_id = request.POST.get('target_admin_id', '') | 
| 1712 | -    admin_type = int(request.POST.get('admin_type', 3)) | |
| 1712 | +    admin_type = int(request.POST.get('admin_type', -1)) | |
| 1713 | 1713 |      phone = request.POST.get('phone', '') | 
| 1714 | 1714 |      name = request.POST.get('name', '') | 
| 1715 | 1715 |      password = request.POST.get('password', '') | 
| @@ -1725,15 +1725,24 @@ def administrator_update(request): | ||
| 1725 | 1725 | if administrator.admin_type != AdministratorInfo.ADMINISTRATOR: | 
| 1726 | 1726 | return response(AdministratorStatusCode.ADMINISTRATOR_PERMISSION_DENIED) | 
| 1727 | 1727 |  | 
| 1728 | - encryption = make_password(strip(password), settings.MAKE_PASSWORD_SALT, settings.MAKE_PASSWORD_HASHER) | |
| 1729 | - | |
| 1730 | - AdministratorInfo.objects.filter(admin_id=target_admin_id, brand_id=brand_id).update( | |
| 1731 | - admin_type=admin_type, | |
| 1732 | - phone=phone, | |
| 1733 | - name=name, | |
| 1734 | - password='', | |
| 1735 | - encryption=encryption, | |
| 1736 | - ) | |
| 1728 | + target_admin = AdministratorInfo.objects.get(admin_id=target_admin_id, status=True) | |
| 1729 | + | |
| 1730 | + if admin_type != -1: | |
| 1731 | + target_admin.admin_type = admin_type | |
| 1732 | + | |
| 1733 | + if phone: | |
| 1734 | + target_admin.phone = phone | |
| 1735 | + | |
| 1736 | + if name: | |
| 1737 | + target_admin.name = name | |
| 1738 | + AdministratorLoginLogInfo.objects.filter(admin_id=target_admin_id).update(admin_name=name) | |
| 1739 | + | |
| 1740 | + | |
| 1741 | + if password: | |
| 1742 | + encryption = make_password(strip(password), settings.MAKE_PASSWORD_SALT, settings.MAKE_PASSWORD_HASHER) | |
| 1743 | + target_admin.encryption = encryption | |
| 1744 | + | |
| 1745 | + target_admin.save() | |
| 1737 | 1746 |  | 
| 1738 | 1747 | return response(200, 'Update Admin Success', u'更新后台管理员成功') | 
| 1739 | 1748 |  | 
| @@ -1757,3 +1766,36 @@ def administrator_delete(request): | ||
| 1757 | 1766 | AdministratorInfo.objects.filter(admin_id=target_admin_id).update(status=False) | 
| 1758 | 1767 |  | 
| 1759 | 1768 | return response(200, 'Delete Admin Success', u'删除后台管理员成功') | 
| 1769 | + | |
| 1770 | + | |
| 1771 | +def administrator_login_list(request): | |
| 1772 | +    brand_id = request.POST.get('brand_id', settings.KODO_DEFAULT_BRAND_ID) | |
| 1773 | +    admin_id = request.POST.get('admin_id', '') | |
| 1774 | +    page = request.POST.get('page', 1) | |
| 1775 | +    num = request.POST.get('num', 20) | |
| 1776 | +    target_admin_id = request.POST.get('target_admin_id', '') | |
| 1777 | + | |
| 1778 | + if brand_id != settings.KODO_DEFAULT_BRAND_ID: | |
| 1779 | + return response(ProductBrandStatusCode.BRAND_NOT_MATCH) | |
| 1780 | + | |
| 1781 | + try: | |
| 1782 | + administrator = AdministratorInfo.objects.get(admin_id=admin_id, user_status=AdministratorInfo.ACTIVATED, status=True) | |
| 1783 | + except AdministratorInfo.DoesNotExist: | |
| 1784 | + return response(AdministratorStatusCode.ADMINISTRATOR_NOT_FOUND) | |
| 1785 | + | |
| 1786 | + if administrator.admin_type != AdministratorInfo.ADMINISTRATOR: | |
| 1787 | + return response(AdministratorStatusCode.ADMINISTRATOR_PERMISSION_DENIED) | |
| 1788 | + | |
| 1789 | +    logs = AdministratorLoginLogInfo.objects.filter(status=True).order_by('-login_at') | |
| 1790 | + | |
| 1791 | + if target_admin_id: | |
| 1792 | + logs = logs.filter(admin_id=target_admin_id) | |
| 1793 | + | |
| 1794 | + count = logs.count() | |
| 1795 | + logs, left = pagination(logs, page, num) | |
| 1796 | + logs = [log.admindata for log in logs] | |
| 1797 | +    return response(200, 'Get Administrator Login List Success', u'获取后台管理员登录日志成功', data={ | |
| 1798 | + 'logs': logs, | |
| 1799 | + 'left': left, | |
| 1800 | + 'count': count | |
| 1801 | + }) | 
| @@ -9,6 +9,7 @@ from django.contrib.auth.hashers import check_password | ||
| 9 | 9 | from django.db import transaction | 
| 10 | 10 | from django_logit import logit | 
| 11 | 11 | from django_response import response | 
| 12 | +from ipaddr import client_ip | |
| 12 | 13 | from pywe_miniapp import get_phone_number | 
| 13 | 14 | from pywe_storage import RedisStorage | 
| 14 | 15 | from TimeConvert import TimeConvert as tc | 
| @@ -16,7 +17,7 @@ from TimeConvert import TimeConvert as tc | ||
| 16 | 17 | from account.models import UserInfo | 
| 17 | 18 | from coupon.models import CouponInfo, UserCouponInfo | 
| 18 | 19 | from integral.models import SaleclerkSubmitLogInfo | 
| 19 | -from logs.models import MchInfoEncryptLogInfo | |
| 20 | +from logs.models import MchInfoEncryptLogInfo, AdministratorLoginLogInfo | |
| 20 | 21 | from mch.models import (ActivityInfo, AdministratorInfo, BrandInfo, ConsumeInfoSubmitLogInfo, DistributorInfo, | 
| 21 | 22 | LatestAppInfo, LatestAppScreenInfo, ModelInfo, OperatorInfo) | 
| 22 | 23 | from statistic.models import ConsumeModelSaleStatisticInfo, ConsumeSaleStatisticInfo, ConsumeUserStatisticInfo | 
| @@ -78,6 +79,14 @@ def admin_login_api(request): | ||
| 78 | 79 | if not check_password(password, administrator.encryption): | 
| 79 | 80 | return response(AdministratorStatusCode.ADMINISTRATOR_PASSWORD_ERROR) | 
| 80 | 81 |  | 
| 82 | + | |
| 83 | + AdministratorLoginLogInfo.objects.create( | |
| 84 | + admin_id=administrator.admin_id, | |
| 85 | + admin_name=administrator.name, | |
| 86 | + login_ip=client_ip(request), | |
| 87 | + login_at=tc.utc_datetime(), | |
| 88 | + ) | |
| 89 | + | |
| 81 | 90 | request.session['admin_id'] = administrator.admin_id | 
| 82 | 91 |  | 
| 83 | 92 |      return response(200, 'Admin Login Success', u'管理员登录成功', data={ | 
| @@ -186,6 +186,9 @@ urlpatterns += [ | ||
| 186 | 186 | url(r'^admin/administrator/create$', admin_views.administrator_create, name='administrator_create'), | 
| 187 | 187 | url(r'^admin/administrator/update$', admin_views.administrator_update, name='administrator_update'), | 
| 188 | 188 | url(r'^admin/administrator/delete$', admin_views.administrator_delete, name='administrator_delete'), | 
| 189 | + | |
| 190 | + url(r'^admin/administrator/login/list$', admin_views.administrator_login_list, name='administrator_login_list'), | |
| 191 | + | |
| 189 | 192 | ] | 
| 190 | 193 |  | 
| 191 | 194 | urlpatterns += [ | 
| @@ -0,0 +1,19 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 3.2.9 on 2022-07-04 12:01 | |
| 3 | + | |
| 4 | +from django.db import migrations, models | |
| 5 | + | |
| 6 | + | |
| 7 | +class Migration(migrations.Migration): | |
| 8 | + | |
| 9 | + dependencies = [ | |
| 10 | +        ('coupon', '0016_auto_20201202_1203'), | |
| 11 | + ] | |
| 12 | + | |
| 13 | + operations = [ | |
| 14 | + migrations.AlterField( | |
| 15 | + model_name='usercouponinfo', | |
| 16 | + name='coupon_from', | |
| 17 | + field=models.CharField(blank=True, db_index=True, default='MEMBER_BENEFITS', help_text=u'劵来源', max_length=32, null=True, verbose_name='coupon_from'), | |
| 18 | + ), | |
| 19 | + ] | 
| @@ -4,7 +4,7 @@ from django.contrib import admin | ||
| 4 | 4 | from django_admin import ReadOnlyModelAdmin | 
| 5 | 5 |  | 
| 6 | 6 | from logs.models import (ComplementCodeLogInfo, MchInfoDecryptLogInfo, MchInfoEncryptLogInfo, MchLogInfo, | 
| 7 | - MchSearchModelAndCameraLogInfo) | |
| 7 | + MchSearchModelAndCameraLogInfo, AdministratorLoginLogInfo) | |
| 8 | 8 |  | 
| 9 | 9 |  | 
| 10 | 10 | class MchInfoEncryptLogInfoAdmin(ReadOnlyModelAdmin, admin.ModelAdmin): | 
| @@ -32,9 +32,14 @@ class ComplementCodeLogInfoAdmin(admin.ModelAdmin): | ||
| 32 | 32 |      list_display = ('user_id', 'log_id', 'name', 'phone', 'model_id', 'model_name', 'sn', 'shot_image', 'invoice_image', 'audit_status', 'ciphertext', 'is_contacted', 'is_upload_qiniu', 'status', 'created_at', 'updated_at') | 
| 33 | 33 |      list_filter = ('model_id', 'audit_status', 'is_contacted', 'is_upload_qiniu', 'status') | 
| 34 | 34 |  | 
| 35 | +class AdministratorLoginLogInfoAdmin(admin.ModelAdmin): | |
| 36 | +    list_display = ('admin_id', 'admin_name', 'login_ip', 'login_at', 'status', 'created_at', 'updated_at') | |
| 37 | +    list_filter = ('admin_id', 'admin_name') | |
| 38 | + | |
| 35 | 39 |  | 
| 36 | 40 | admin.site.register(MchInfoDecryptLogInfo, MchInfoDecryptLogInfoAdmin) | 
| 37 | 41 | admin.site.register(MchInfoEncryptLogInfo, MchInfoEncryptLogInfoAdmin) | 
| 38 | 42 | admin.site.register(MchSearchModelAndCameraLogInfo, MchSearchModelAndCameraLogInfoAdmin) | 
| 39 | 43 | admin.site.register(MchLogInfo, MchLogInfoAdmin) | 
| 40 | 44 | admin.site.register(ComplementCodeLogInfo, ComplementCodeLogInfoAdmin) | 
| 45 | +admin.site.register(AdministratorLoginLogInfo, AdministratorLoginLogInfoAdmin) | 
| @@ -0,0 +1,30 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 3.2.9 on 2022-07-04 12:01 | |
| 3 | + | |
| 4 | +from django.db import migrations, models | |
| 5 | + | |
| 6 | + | |
| 7 | +class Migration(migrations.Migration): | |
| 8 | + | |
| 9 | + dependencies = [ | |
| 10 | +        ('logs', '0017_complementcodeloginfo_is_upload_qiniu'), | |
| 11 | + ] | |
| 12 | + | |
| 13 | + operations = [ | |
| 14 | + migrations.CreateModel( | |
| 15 | + name='AdministratorLoginLogInfo', | |
| 16 | + fields=[ | |
| 17 | +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
| 18 | +                ('status', models.BooleanField(default=True, help_text='Status', verbose_name='status')), | |
| 19 | +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')), | |
| 20 | +                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')), | |
| 21 | +                ('admin_id', models.CharField(blank=True, help_text='管理员唯一标识', max_length=32, null=True, verbose_name='admin_id')), | |
| 22 | +                ('admin_name', models.CharField(blank=True, help_text='管理员姓名', max_length=255, null=True, verbose_name='name')), | |
| 23 | +                ('login_ip', models.CharField(blank=True, help_text='登录IP', max_length=32, null=True, verbose_name='login_ip')), | |
| 24 | +                ('login_at', models.DateTimeField(blank=True, help_text='登录时间', null=True, verbose_name='login_at')), | |
| 25 | + ], | |
| 26 | +            options={ | |
| 27 | + 'abstract': False, | |
| 28 | + }, | |
| 29 | + ), | |
| 30 | + ] | 
| @@ -193,3 +193,19 @@ class ComplementCodeLogInfo(BaseModelMixin): | ||
| 193 | 193 | 'ciphertext': self.ciphertext, | 
| 194 | 194 | 'created_at': tc.local_string(utc_dt=self.created_at, format='%Y-%m-%d %H:%M:%S') | 
| 195 | 195 | } | 
| 196 | + | |
| 197 | +class AdministratorLoginLogInfo(BaseModelMixin): | |
| 198 | + admin_id = models.CharField(_(u'admin_id'), max_length=32, blank=True, null=True, help_text=u'管理员唯一标识') | |
| 199 | + admin_name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'管理员姓名') | |
| 200 | + login_ip = models.CharField(_(u'login_ip'), max_length=32, blank=True, null=True, help_text=_(u'登录IP')) | |
| 201 | + login_at = models.DateTimeField(_(u'login_at'), blank=True, null=True, help_text=_(u'登录时间')) | |
| 202 | + | |
| 203 | + | |
| 204 | + @property | |
| 205 | + def admindata(self): | |
| 206 | +        return { | |
| 207 | + 'admin_id': self.admin_id, | |
| 208 | + 'admin_name': self.admin_name, | |
| 209 | + 'login_ip': self.login_ip, | |
| 210 | + 'login_at': tc.local_string(utc_dt=self.login_at, format='%Y-%m-%d %H:%M:%S'), | |
| 211 | + } | 
| @@ -0,0 +1,60 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 3.2.9 on 2022-07-04 12:01 | |
| 3 | + | |
| 4 | +from django.db import migrations, models | |
| 5 | +import simditor.fields | |
| 6 | + | |
| 7 | + | |
| 8 | +class Migration(migrations.Migration): | |
| 9 | + | |
| 10 | + dependencies = [ | |
| 11 | +        ('member', '0031_auto_20211124_1850'), | |
| 12 | + ] | |
| 13 | + | |
| 14 | + operations = [ | |
| 15 | + migrations.AlterField( | |
| 16 | + model_name='memberactivityinfo', | |
| 17 | + name='brand_id', | |
| 18 | + field=models.CharField(blank=True, db_index=True, default='', help_text='品牌唯一标识', max_length=32, verbose_name='brand_id'), | |
| 19 | + ), | |
| 20 | + migrations.AlterField( | |
| 21 | + model_name='memberactivityinfo', | |
| 22 | + name='brand_name', | |
| 23 | + field=models.CharField(blank=True, default='', help_text='品牌名称', max_length=255, verbose_name='brand_name'), | |
| 24 | + ), | |
| 25 | + migrations.AlterField( | |
| 26 | + model_name='memberactivityinfo', | |
| 27 | + name='city', | |
| 28 | + field=models.CharField(blank=True, default='', help_text='活动城市', max_length=255, verbose_name='city'), | |
| 29 | + ), | |
| 30 | + migrations.AlterField( | |
| 31 | + model_name='memberactivityinfo', | |
| 32 | + name='content_rich_text', | |
| 33 | + field=simditor.fields.RichTextField(blank=True, default='', help_text='活动描述', verbose_name='content_rich_text'), | |
| 34 | + ), | |
| 35 | + migrations.AlterField( | |
| 36 | + model_name='memberactivityinfo', | |
| 37 | + name='location', | |
| 38 | + field=models.CharField(blank=True, default='', help_text='活动地点', max_length=255, verbose_name='location'), | |
| 39 | + ), | |
| 40 | + migrations.AlterField( | |
| 41 | + model_name='memberactivityinfo', | |
| 42 | + name='share_h5_link', | |
| 43 | + field=models.CharField(blank=True, default='', help_text='活动H5分享', max_length=255, verbose_name='share_h5_link'), | |
| 44 | + ), | |
| 45 | + migrations.AlterField( | |
| 46 | + model_name='memberactivityinfo', | |
| 47 | + name='share_img_link', | |
| 48 | + field=models.CharField(blank=True, default='', help_text='活动图片分享', max_length=255, verbose_name='share_img_link'), | |
| 49 | + ), | |
| 50 | + migrations.AlterField( | |
| 51 | + model_name='memberactivityinfo', | |
| 52 | + name='subtitle', | |
| 53 | + field=models.CharField(blank=True, default='', help_text='活动二级名称', max_length=255, verbose_name='subtitle'), | |
| 54 | + ), | |
| 55 | + migrations.AlterField( | |
| 56 | + model_name='memberactivityinfo', | |
| 57 | + name='title', | |
| 58 | + field=models.CharField(blank=True, default='', help_text='活动名称', max_length=255, verbose_name='title'), | |
| 59 | + ), | |
| 60 | + ] | 
| @@ -20,7 +20,7 @@ django-redis-connector==1.0.4 | ||
| 20 | 20 | django-response==1.1.1 | 
| 21 | 21 | django-rlog==1.0.7 | 
| 22 | 22 | django-shortuuidfield==0.1.3 | 
| 23 | -# django-simpleui==3.9.1 | |
| 23 | +django-simpleui==3.9.1 | |
| 24 | 24 | django-six==1.0.4 | 
| 25 | 25 | django-uniapi==1.0.10 | 
| 26 | 26 | django-we==1.5.6 |