Django admin后台管理

admin后台维护模型

  • 创建模型
  • 在应用的admin模块注册模型 admin.site.register(模型类[, 模型管理类])
  • 可以在根urls模块设置admin.site.site_header等信息,修改后台显示的系统名称

模型admin类

  • 在应用admin模块为模型类创建对应的模型管理类,继承admin.ModelAdmin
  • 通过模型管理类可以进行定制话管理
  • exclude:在编辑页面中不显示的模型字段
  • list_display:在模型列表页显示的模型字段,元素可以是模型字段名,或者模型admin里面的函数名,如果是函数名,展示的就是函数返回的字符串,还可以使用mark_safe返回 < a > 的html内容,这样就可以生成跳转链接了
  • fieldsets:可以对表单字段设置分组(('分组名称', {'fields': (本组的字段,可以嵌套一层集合,表示那些字段绘制在同一行)}),
    (第二组...)...),对应的方法是get_fieldsets(self, request, obj=None)
  • list_filter:用来过滤的字段
  • search_fields:用来搜索查询的字段
  • ordering:设置可以排序的字段
  • readonly_fields:可以设置admin后台表单中只读的字段,对应的方法是get_readonly_fields(self, request, obj),这样可以根据当前登录用户、角色等条件进行定制处理
  • list_editable:在列表页面可编辑的字段,这个字段没有对应的方法,但是可以在get_changelist_instance(self, request)中对进行动态设置:self.ediable = xxx
  • get_queryset():列表页默认调用此方法获取数据,可以在此方法中根据登录用户、角色等条件进行定制处理
  • save_model():模型保存时会自动调用此方法,可以做一些特殊处理,比如自动给用户字段赋值,自动给给更新时间字段赋值等

权限设置

  • 可以在模型的Meta类中,通过permissions属性给模型自定义权限(定义完权限,需要makemigrations、 migrate迁移操作),
  • 然后在模型admin中,通过 功能函数.allowed_permissions = ('xxx', 'xxx', ...) 把功能与权限进行关联,
  • 同时需要实现 has_xxx_permission(self, request) 方法,判断用户是否有对应的权限,方法名中xxx对应的就是模型permissions中设置的权限名称,
  • 然后在admin后台的权限管理里面,就可以给group或者user设置对应的权限了

系统自带用户模型User

  • 判断是否为超级用户:user.is_superuser
  • 获取用户所属group:user.groups.all()

自定义django命令

  • 可以在应用下创建目录 management/commands ,然后在目录下新增模块,并在模块中定义Command类,继承BaseCommand类,这样,模块名就是命令名,可以使用python manage.py 模块名 xxx 来执行自定义命令

多语言设置

  • 代码中使用gettext或者gettext_lazy获取多语言资源对应的文本内容
  • 可以使用 # Translators: 注释内容 这样在生成资源文件的时候会带上这段注释内容
  • 在模板中使用 {% translate "xxx" %}获取多语言文本
  • 在模板中使用 {% blocktranslate with xxx=yyy%} {{xxx}}
  • 生成多语言资源文件
    • 在工程目录下创建locale目录
    • 使用命令创建:django-admin makemessages -l zh_HANS -l en 需要更多语言就继续添加 -l xxx
    • 这样在locale目录下会生成zh_HANS和en两个种语言的目录
  • 翻译多语言内容
    • 在生成的语言目录下分别用对应的语言翻译各语言文件
  • 生成二进制多语言资源文件
    • django-admin compilemessages 生成二进制语言文件
  • 在settings中添加多语言配置
    from django.utils.translation import gettext_lazy as _
    # 系统支持的语言种类
    LANGUAGES = [
        ('zh-hans', _('Chinese')),
        ('en', _('English')),
    ]
    
    LANGUAGE_CODE = 'zh-hans'
    
    TIME_ZONE = 'Asia/Shanghai'
    
    USE_I18N = True
    
    USE_L10N = True
    
    USE_TZ = True
    # 多语言文件路径
    LOCALE_PATHS = (
        os.path.join(BASE_DIR, 'locale'),
    )
    
  • 在页面中提供语言切换功能
    • 在主urls模块添加路由:path('i18n/', include('django.conf.urls.i18n'))
    • 添加路由中间件
      'django.contrib.sessions.middleware.SessionMiddleware',  # 它之后
      'django.middleware.locale.LocaleMiddleware', # 加这个
      'django.middleware.cache.UpdateCacheMiddleware',  # 它之前
    
    • 在页面添加切换语言功能,切换功能表单是:
      <form action="{% url 'set_language' %}" method="post" > ... </form>
      其中set_language是在i18n路由中提供的
    
  • django识别语言类型的顺序:url > cookie > 请求头 > settings里面设置的LANGUAGE_CODE

安全

XSS跨站脚本攻击

  • 用户在网页中提交了带有js脚本的文本,比如评论信息
  • 服务端没有做处理,之间把带js脚本的数据保存到了数据库
  • 当有其他用户访问到还有对应数据的页面时,js脚本将以当前访问用户的身份被执行
  • django的处理方式:
    • 模板使用html文件,通过使用render函数进行加载渲染后返回
    • 如果要直接返回字符串,可使用python标准库html,然后使用html.escape(xxx)对xxx字符串进行处理,这样界面就会直接展示js脚本,而不是被浏览器js引擎加载执行
    • 对用户提交的数据进行校验,避免提交不合法数据

CSRF跨站请求伪造

  • 黑客在自己的网站中通过链接、表单等方式链接到了A网站
  • 用户访问到黑客的网站是,被诱导点击了带有访问A网站的链接或者表单(用户不知道背后请求的是A网站)
  • 这时,浏览器就会携带用户在A网站的cookie请求A网站,这种情况下,黑客就可以以用户的身份对A网站进行操作,比如银行转账等等
  • 所有,如果在浏览器中登录了银行之类的网站,最好及时登出,一般登出的时候,会清理掉cookie等信息
  • django的处理方式:
    • 添加中间件django.middleware.csrf.CsrfViewMiddleware,默认是添加好的
    • 在模板表单中使用{% csrf_token %},这样就会在渲染表单时,添加隐藏的input,比如:
    <input type="hidden" name="csrfmiddlewaretoken" value="Lw0GbBUXucFQNnPkdLCnfrirdY3UqvdfLwZPH00DNEU9Iu9nYyvcIYQH4FY6C1qF">
    
    • 同时浏览器的中会存在名称为csrftoken的cookie,
    • 表单提交的时候就会在表单数据中传递csrfmiddlewaretoken到服务器,同时请求的cookie中会带上csrftoken
    • 中间件django.middleware.csrf.CsrfViewMiddleware在处理请求的时候就会判断表单csrfmiddlewaretoken与cookie csrftoken的值是否一致
    • 目前新版本的csrfmiddlewaretoken的值是64位的,前32位是mask,后32位是token,以token的每一个字符减去对应的mask位置的字符的差作为索引,到字符字典中查对应的字符,最后拼接的结果与csrftoken的值进行比较
    def _unmask_cipher_token(token):
        """ 官方解析64位csrfmiddlewaretoken源码
        Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
        CSRF_TOKEN_LENGTH, and that its first half is a mask), use it to decrypt
        the second half to produce the original secret.
        """
        mask = token[:CSRF_SECRET_LENGTH]
        token = token[CSRF_SECRET_LENGTH:]
        chars = CSRF_ALLOWED_CHARS
        pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in mask))
        return "".join(chars[x - y] for x, y in pairs)  # Note negative values are ok
    
    • 如果要使视图不被csrf保护,则可以对视图函数使用csrf_exempt装饰器(from django.views.decorators.csrf import csrf_exempt)

sql注入

  • 使用orm操作数据
  • 直接使用sql使,通过变量绑定的方式,尽量不要使用字符串拼接的方式

中间件

自定义中间件

# 使用函数的方式
def performance_logger_middleware(get_response):
    def middleware(request):
        start_time = time.time()
        response = get_response(request)  # 在get_response前后处理
        duration = time.time() - start_time
        response["X-Page-Duration-ms"] = int(duration * 1000)
        logger.info("%s %s %s", duration, request.path, request.GET.dict() )
        return response

    return middleware


# 使用类的方式
# 还可以通过继承from django.utils.deprecation import MiddlewareMixin,然后实现process_request, process_response等方法
class PerformanceAndExceptionLoggerMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        start_time = time.time()
        response = self.get_response(request)
        duration = time.time() - start_time
        response["X-Page-Duration-ms"] = int(duration * 1000)
        logger.info("duration:%s url:%s parameters:%s", duration, request.path, request.GET.dict() )

        # Code to be executed for each request/response after
        # the view is called.

        return response

    def process_exception(self, request, exception):
        if exception:
                
            message = "url:{url} ** msg:{error} ````{tb}````".format(
                url = request.build_absolute_uri(),
                error = repr(exception),
                tb = traceback.format_exc()
            )
            
            logger.warning(message)
            
            # send dingtalk message
            dingtalk.send(message)

            # capture exception to sentry:
            capture_exception(exception)
                
        return HttpResponse("Error processing the request, please contact the system administrator.", status=500)

django-redis

  • 用作缓存
  • 缓存配置:
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://192.168.139.133:6379/0",
        'TIMEOUT': 60, # default expire time per api call
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "SOCKET_CONNECT_TIMEOUT": 5,  # in seconds
            "SOCKET_TIMEOUT": 5,  # r/w timeout in seconds
            'MAX_ENTRIES': 10000,
            'KEY_PREFIX': 'recruit-',
            # "PASSWORD": "mysecret",
        }
    }
}
  • 中间件配置
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.cache.UpdateCacheMiddleware',  # 更新缓存
    'django.middleware.common.CommonMiddleware',  # 放在这个的前后
    'django.middleware.cache.FetchFromCacheMiddleware',  # 从缓存获取
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

集成celery

CELERY_BROKER_URL = 'redis://192.168.139.133:6379/8'
CELERY_RESULT_BACKEND = 'redis://192.168.139.133:6379/1'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Shanghai'
  • 然后在各应用添加tasks.py,celery会自动扫描所有注册的应用下的tasks
# 一个发送简单邮件的示例
'''
# django中邮件相关参数配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
EMAIL_HOST_USER = 'xxx@163.com'
EMAIL_HOST_PASSWORD = 'xxx'
DEFAULT_FROM_EMAIL = 'xxx@163.com'
'''

from celery import shared_task
from django.core.mail import send_mail


@shared_task
def send_email(email_to, subject, message, from_email=None):
    send_mail(subject, message, from_email, email_to, fail_silently=False)

  • 启动celery worker, 注意:windows下需要使用 -P gevent,不然任务执行不了
celery -A 应用名称 worker -l info -P gevent
  • 可以借助flower对任务进行监控
    • 安装:pip install flower
    • 启动: celery -A 应用名称 flower

django-celery-beat

  • 可以在admin后台管理定时任务
  • 安装 pip install django-celery-beat
  • 启用
INSTALLED_APPS = [
    ...,
    'django_celery_beat',
]
  • 数据库迁移:python manage.py migrate django_celery_beat
  • 启动定时任务: celery -A 应用名称 beat --scheduler django_celery_beat.schedulers.DatabaseScheduler
  • 添加定时任务
    • 在admin后台直接添加,保存到数据库表中
    • 在配置中添加
from celery import shared_task
@shared_task
def schedule_test(msg):
    print(msg)


app = Celery('wqb_dj')

# 在配置中添加
app.conf.beat_schedule = {
    'add-every-10-seconds': {
        'task': 'wqb_dj.tasks.add',
        'schedule': 10.0,
        'args': (16, 4, )
    },
}
  • 监听信号,在系统启动结束后添加
app = Celery('wqb_dj')

@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    # Calls schedule_test('hello') every 10 seconds.
    sender.add_periodic_task(10.0, schedule_test.s('hello'), name='hello every 10')

    # Calls test('world') every 30 seconds
    sender.add_periodic_task(30.0, schedule_test.s('world'), expires=10)

    # Executes every Monday morning at 7:30 a.m.
    sender.add_periodic_task(
        crontab(hour=19, minute=30, day_of_week=1),
        schedule_test.s('Happy Mondays!'),
    )
  • 在系统运行期间,业务运行时动态添加
import json
from django_celery_beat.models import PeriodicTask, IntervalSchedule

def func_test_add_schedule_task():
    """动态的增加定时任务"""

    # 通过周期任务相关的模型类,在运行时添加
    # 先创建定时策略
    schedule, created = IntervalSchedule.objects.get_or_create(every=10, period=IntervalSchedule.SECONDS, )
    # 再创建任务
    msg = time.time()
    PeriodicTask.objects.create(interval=schedule, name=f'say welcome {msg}', task='wqb_dj.tasks.schedule_test',
                                args=json.dumps([f'welcome {msg}']), )

图片与文件

  • 在配置文件中定义
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')  # 媒体存放目录
MEDIA_URL = '/media/'  # 媒体请求路径
  • 在主urls.py模块定义路由
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  • 图片:模型中的字段定义使用:models.ImageField(upload_to='图片存储目录/', blank=True, verbose_name='图片')
  • 文件:模型中的字段定义使用:models.FileField(upload_to='文件存储目录/', blank=True, verbose_name='文件')
  • 最后访问文件或图片的路径就是:/media/图片(文件)存储目录/图片(文件)名
  • 还可以使用oss存储
# 阿里云 CDN 存储静态资源文件 & 阿里云存储上传的图片/文件
# STATICFILES_STORAGE = 'django_oss_storage.backends.OssStaticStorage'

DEFAULT_FILE_STORAGE = 'django_oss_storage.backends.OssMediaStorage'

# AliCloud access key ID
OSS_ACCESS_KEY_ID = os.environ.get('OSS_ACCESS_KEY_ID','')
# AliCloud access key secret
OSS_ACCESS_KEY_SECRET = os.environ.get('OSS_ACCESS_KEY_SECRET','')
# The name of the bucket to store files in
OSS_BUCKET_NAME = 'xxx'

# The URL of AliCloud OSS endpoint
# Refer https://www.alibabacloud.com/help/zh/doc-detail/31837.htm for OSS Region & Endpoint
OSS_ENDPOINT = 'oss-cn-beijing.aliyuncs.com'
posted @ 2025-02-14 11:28  liDB  阅读(35)  评论(0)    收藏  举报