├── .gitattributes ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── _config.yml ├── app ├── __init__.py ├── admin.py ├── app_models │ ├── __init__.py │ ├── config_model.py │ ├── content_model.py │ └── other_model.py ├── apps.py ├── consts.py ├── db_manager │ ├── __init__.py │ ├── config_manager.py │ ├── content_manager.py │ └── other_manager.py ├── deeru_config_handler │ ├── __init__.py │ ├── base.py │ ├── handler.py │ └── manager.py ├── deeru_expression │ ├── __init__.py │ ├── expressions.py │ └── manager.py ├── ex_admins │ ├── __init__.py │ ├── admin.py │ └── list_filter.py ├── ex_fields │ ├── __init__.py │ ├── fields.py │ └── widgets.py ├── ex_paginator.py ├── forms.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── import_wordpress.py │ │ ├── init_deeru.py │ │ └── temp.py ├── manager │ ├── __init__.py │ ├── article.py │ ├── config_manager.py │ ├── config_manager_v2.py │ ├── content_manager.py │ ├── ct_manager.py │ ├── email.py │ ├── expression_manager.py │ ├── theme.py │ └── uiconfig_manager.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20180716_1327.py │ ├── 0003_auto_20191207_1737.py │ ├── 0004_auto_20191207_1737.py │ ├── 0005_article_sorted_num.py │ └── __init__.py ├── models.py ├── signals │ ├── __init__.py │ └── handlers.py ├── sitemap.py ├── static │ └── json_editor │ │ └── css │ │ └── spectre.css ├── templates │ └── app │ │ ├── admin │ │ ├── article_change_form.html │ │ ├── article_title.html │ │ ├── category_change_list.html │ │ ├── comment_author.html │ │ ├── comment_content.html │ │ ├── comment_status.html │ │ ├── config.html │ │ └── flatpage_title.html │ │ └── email │ │ └── comment_email.html ├── tests.py ├── urls │ ├── __init__.py │ ├── flatpage_url.py │ └── urls.py └── views │ ├── __init__.py │ ├── views.py │ ├── views_class.py │ └── views_v2.py ├── base_theme ├── __init__.py ├── apps.py ├── manager │ ├── __init__.py │ └── base_theme_manager.py ├── static │ └── base_theme │ │ ├── css │ │ ├── base_theme.css │ │ └── jodit.es2018.min.css │ │ └── js │ │ ├── base_theme.js │ │ ├── jodit.es2018.min.js │ │ └── layer.js ├── templates │ └── base_theme │ │ ├── 404.html │ │ ├── aside_right.html │ │ ├── base.html │ │ ├── body_footer.html │ │ ├── body_navbar.html │ │ ├── category.html │ │ ├── detail_article.html │ │ ├── detail_flatpage.html │ │ ├── head.html │ │ ├── home.html │ │ └── tag.html ├── templatetags │ ├── __init__.py │ └── base_theme_filter.py └── tests.py ├── base_theme2 ├── __init__.py ├── apps.py ├── static │ └── base_theme2 │ │ ├── css │ │ ├── base_theme2.css │ │ └── jodit.es2018.min.css │ │ ├── img │ │ └── avatar.jpg │ │ └── js │ │ ├── comment-editor.js │ │ ├── jodit.es2018.min.js │ │ └── layer.js ├── templates │ └── base_theme2 │ │ ├── 404.html │ │ ├── article_list.html │ │ ├── article_list_breadcrumb.html │ │ ├── article_list_empty_item.html │ │ ├── article_list_item.html │ │ ├── base2.html │ │ ├── body_footer.html │ │ ├── body_navbar.html │ │ ├── body_navbar_left.html │ │ ├── body_navbar_menu.html │ │ ├── body_navbar_right.html │ │ ├── body_section.html │ │ ├── body_section_sidebar.html │ │ ├── detail_article.html │ │ ├── detail_article_comment.html │ │ ├── detail_article_content.html │ │ ├── detail_article_head_begin.html │ │ ├── detail_flatpage.html │ │ ├── empty.html │ │ └── head_static.html ├── templatetags │ ├── __init__.py │ └── base_theme2_filter.py ├── tests.py └── theme.py ├── building.png ├── data └── Alibaba-PuHuiTi-Regular.ttf ├── deeru ├── __init__.py ├── settings.py ├── settings_common.py ├── urls.py └── wsgi.py ├── deeru_cmd ├── __init__.py ├── app_templates │ ├── MANIFEST.in-tpl │ ├── README.rst-tpl │ ├── apps.py-tpl │ ├── consts.py-tpl │ ├── empty.py-tpl │ ├── git_add.py-tpl │ └── setup.py-tpl ├── apps.py ├── bin │ └── deeru_admin.py ├── management │ ├── __init__.py │ ├── base.py │ └── commands │ │ ├── __init__.py │ │ ├── install.py │ │ ├── start.py │ │ ├── starttheme.py │ │ └── upgrade.py ├── project_templates │ ├── settings_local.py-tpl │ └── urls_local.py-tpl └── tests.py ├── deeru_dashboard ├── __init__.py ├── apps.py ├── dashboard.py ├── dashboard_modules.py ├── models.py ├── templates │ └── deeru_dashboard │ │ ├── comment.html │ │ └── count.html ├── templatetags │ ├── __init__.py │ └── dashboard_tags.py ├── tests.py └── views.py ├── deeru_green.png ├── docs ├── Makefile ├── _static │ ├── admin.jpg │ ├── admin.png │ ├── admin2.png │ ├── admin3.png │ ├── config.jpg │ ├── deeru_green.png │ ├── detail.png │ ├── home.png │ ├── logo_black.png │ ├── p1.png │ ├── p2.png │ ├── p3.png │ ├── templates.jpg │ └── ui_config.png ├── change_log.rst ├── conf.py ├── developer_guide │ ├── config │ │ ├── handler.rst │ │ └── index.rst │ ├── contribution.rst │ ├── expression │ │ ├── custom_expression.rst │ │ ├── format_expression.rst │ │ └── index.rst │ ├── index.rst │ ├── model │ │ ├── content_model.rst │ │ └── index.rst │ ├── role.rst │ ├── theme │ │ ├── context.rst │ │ ├── index.rst │ │ ├── other.rst │ │ ├── templates.rst │ │ ├── theme.rst │ │ └── url_view.rst │ └── third_party │ │ ├── index.rst │ │ └── plugin.rst ├── index.rst ├── installation.rst ├── make.bat ├── upgrade.rst └── user_guide │ ├── backup_and_restore.rst │ ├── configuration │ └── index.rst │ ├── deployment │ ├── gunicorn.rst │ └── index.rst │ ├── getting_started.rst │ ├── index.rst │ ├── management_commands.rst │ ├── rich_text_editor.rst │ ├── settings.rst │ └── sitemap.rst ├── install.py ├── logo_black.png ├── logo_white.png ├── manage.py ├── requirements.txt ├── setup.py └── tool ├── __init__.py ├── captcha.py ├── datetime_helper.py ├── deeru_exceptions.py ├── deeru_html.py ├── deeru_math.py ├── html_helper.py ├── logger.py ├── secure.py ├── sign.py └── version_upgrade ├── __init__.py └── v1_config_to_v2.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.js linguist-vendored 3 | *.css linguist-vendored 4 | *.html linguist-vendored 5 | *.py linguist-vendored=false 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | .idea/ 109 | /static 110 | /media 111 | 112 | files.txt 113 | 114 | deeru/settings_local.py 115 | deeru/urls_local.py 116 | 117 | .qiniu_pythonsdk_hostscache.json 118 | *.ipynb 119 | json_html 120 | *.sqlite3* 121 | 122 | base_theme_ad 123 | 124 | custom_* -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include MANIFEST.in 3 | graft deeru_cmd 4 | global-exclude __pycache__ 5 | global-exclude *.py[co] -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://github.com/gojuukaze/DeerU/blob/master/logo_black.png?raw=true 2 | :target: https://github.com/gojuukaze/DeerU 3 | :scale: 50% 4 | 5 | `DeerU `__ is a content management system, used for blogs. 6 | 7 | DeerU 是一个开源博客系统 8 | 9 | 10 | 依赖 11 | ---- 12 | 13 | - Python 3.6+ -- 安装教程 https://www.ikaze.cn/article/28 14 | - pip 10+ 15 | - git 16 | - libjpeg,zlib -- pillow包的依赖 17 | 18 | - ubuntu: 19 | ``apt-get install libjpeg8-dev zlib1g-dev libfreetype6-dev`` 20 | - centos: 21 | ``yum -y install python-devel zlib-devel libjpeg-turbo-devel`` 22 | 23 | 目录 24 | ---- 25 | 26 | - 项目文档 :\ https://deeru.readthedocs.io 27 | - `安装 <#安装>`__ 28 | - `初始化 <#初始化>`__ 29 | - `运行 <#运行>`__ 30 | 31 | 安装 32 | ---- 33 | 34 | - 安装之前建议配置虚拟环境 35 | 36 | .. code:: bash 37 | 38 | 39 | pip install virtualenv 40 | virtualenv --no-site-packages deeru_env 41 | source deeru_env/bin/activate 42 | 43 | - pip安装 44 | 45 | .. code:: bash 46 | 47 | pip install DeerU 48 | deeru-admin install deeru 49 | 50 | - 手动安装 51 | 52 | .. code:: bash 53 | 54 | 55 | git clone -b dev https://github.com/gojuukaze/DeerU.git 56 | cd DeerU 57 | pip install -r requirements.txt 58 | 59 | 初始化 60 | ------ 61 | 62 | - 运行下面命令初始化项目,注意:如果你更改了数据库的配置,或者修改了主题的静态文件 63 | 则需要再次运行初始化 64 | 65 | .. code:: bash 66 | 67 | 68 | cd DeerU # 如果你没进入工程目录先进入 69 | python manage.py init_deeru 70 | 71 | 运行 72 | ---- 73 | 74 | - 以debug模式运行 75 | 76 | .. code:: bash 77 | 78 | python manage.py runserver 0.0.0.0:8000 79 | 80 | license 81 | ---------- 82 | 83 | DeerU使用 `GNU General Public License v3.0 84 | 协议 `__ 85 | ,你可以在遵循此协议的情况下免费使用DeerU 86 | 87 | .. note:: 88 | 89 | 需要注意的是,DeerU本身是免费的,但后台管理使用了富文本编辑器froala,其扩展插件并不免费,你可以在以下链接中查看收费信息: 90 | 91 | https://github.com/froala/django-froala-editor#license 92 | 93 | https://froala.com/wysiwyg-editor/pricing 94 | 95 | (你可以自己更换其他编辑器,我也会在之后内置一些富文本编辑器的替代方案) 96 | 97 | 截图 98 | ---- 99 | 100 | 首页 101 | 102 | .. image:: https://github.com/gojuukaze/DeerU/blob/dev/docs/source/_static/home.png?raw=true 103 | :scale: 80% 104 | 105 | .. image:: https://github.com/gojuukaze/DeerU/blob/dev/docs/source/_static/detail.png?raw=true 106 | :scale: 80% 107 | 108 | .. image:: https://github.com/gojuukaze/DeerU/blob/dev/docs/source/_static/admin.png?raw=true 109 | :scale: 80% 110 | 111 | .. image:: https://github.com/gojuukaze/DeerU/blob/dev/docs/source/_static/admin3.png?raw=true 112 | :scale: 80% 113 | 114 | .. image:: https://github.com/gojuukaze/DeerU/blob/dev/docs/source/_static/p2.png?raw=true 115 | :scale: 50% 116 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-time-machine -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'app.apps.MAppConfig' 2 | -------------------------------------------------------------------------------- /app/app_models/__init__.py: -------------------------------------------------------------------------------- 1 | def get_field(name): 2 | import importlib 3 | temp = name.split('.') 4 | 5 | module_name = '.'.join(temp[:-1]) 6 | module = importlib.import_module(module_name) 7 | return getattr(module, temp[-1]) 8 | 9 | -------------------------------------------------------------------------------- /app/app_models/config_model.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | from app.app_models import get_field 4 | from jsonfield import JSONField 5 | from app.ex_fields.fields import ConfigField, ConfigFieldV2 6 | 7 | __all__ = ['Config', 'Version'] 8 | 9 | 10 | class Config(models.Model): 11 | class Meta: 12 | verbose_name = '配置' 13 | verbose_name_plural = '配置' 14 | 15 | name = models.CharField(verbose_name='配置名称', max_length=20, unique=True) 16 | # -- 17 | # 下面三是v1配置,会在之后的版本升级中删除 18 | config = ConfigField(verbose_name='v1版配置', null=True, default='') 19 | last_config = ConfigField(verbose_name='v1版旧配置', blank=True, null=True, default='') 20 | cache = models.TextField(verbose_name='解析后的config', null=True, blank=True, editable=False) 21 | # -- 22 | 23 | # v2新增 24 | v2_config = ConfigFieldV2(verbose_name='v2版配置', null=True, blank=True, dump_kwargs={'ensure_ascii': False}, 25 | default={}) 26 | 27 | # 经过handler处理后的配置,v2_config中的配置有的需要经过handler解析后才能使用 28 | # 保存配置时会自动处理v2_config并把结果保存到这 29 | v2_real_config = JSONField(verbose_name='解析后的config', null=True, blank=True, editable=False, 30 | dump_kwargs={'ensure_ascii': False}) 31 | 32 | v2_schema = models.TextField(verbose_name='json-editor配置', null=True) 33 | # v2_script:js代码,会添加到创建json-editor之后,用于自定义配置的ui等 34 | v2_script = models.TextField(verbose_name='js代码', default='', blank=True) 35 | created_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) 36 | modified_time = models.DateTimeField(verbose_name="修改时间", auto_now=True) 37 | 38 | def set_post_save_flag(self, v): 39 | self.first_config_post_save = v 40 | 41 | def get_post_save_flag(self): 42 | return getattr(self, 'first_config_post_save', True) 43 | 44 | 45 | class Version(models.Model): 46 | class Meta: 47 | verbose_name = '版本' 48 | verbose_name_plural = '版本' 49 | 50 | version = models.CharField('版本号', max_length=20, editable=False) 51 | 52 | created_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) 53 | modified_time = models.DateTimeField(verbose_name="修改时间", auto_now=True) 54 | -------------------------------------------------------------------------------- /app/app_models/other_model.py: -------------------------------------------------------------------------------- 1 | # fs = FileSystemStorage(location='/media/photos') 2 | from django.db import models 3 | from django.utils.translation import ugettext as _ 4 | 5 | __all__ = ['Album'] 6 | 7 | 8 | class Album(models.Model): 9 | class Meta: 10 | verbose_name = _('图片') 11 | verbose_name_plural = _('图片') 12 | 13 | name = models.CharField(verbose_name=_('文件名'), max_length=50, blank=True) 14 | 15 | # photo = models.ImageField(storage=fs) 16 | img = models.ImageField(verbose_name=_('图片')) 17 | -------------------------------------------------------------------------------- /app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MAppConfig(AppConfig): 5 | name = 'app' 6 | 7 | deeru_type = 'project' 8 | deeru_config_context = 'app.consts.app_config_context' 9 | 10 | def ready(self): 11 | # signals are imported, so that they are defined and can be used 12 | import app.signals.handlers 13 | -------------------------------------------------------------------------------- /app/db_manager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/db_manager/__init__.py -------------------------------------------------------------------------------- /app/db_manager/config_manager.py: -------------------------------------------------------------------------------- 1 | from app.app_models.config_model import Config 2 | 3 | 4 | def get_config_by_name(name): 5 | """ 6 | :rtype: Config 7 | """ 8 | try: 9 | return Config.objects.get(name=name) 10 | except: 11 | return None 12 | 13 | 14 | def get_config_by_id(id): 15 | """ 16 | :rtype: Config 17 | """ 18 | try: 19 | return Config.objects.get(id=id) 20 | except: 21 | return None 22 | -------------------------------------------------------------------------------- /app/db_manager/other_manager.py: -------------------------------------------------------------------------------- 1 | from app.app_models.other_model import Album 2 | 3 | 4 | def get_all_image(): 5 | return Album.objects.all() 6 | 7 | 8 | def create_image(name, img): 9 | return Album.objects.create(name=name, img=img) 10 | 11 | 12 | def get_image_by_id(id): 13 | try: 14 | return Album.objects.get(id=id) 15 | except: 16 | return None 17 | 18 | 19 | def filter_image_by_start_name(name): 20 | return Album.objects.filter(img__startswith=name) 21 | -------------------------------------------------------------------------------- /app/deeru_config_handler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/deeru_config_handler/__init__.py -------------------------------------------------------------------------------- /app/deeru_config_handler/base.py: -------------------------------------------------------------------------------- 1 | class BaseHandler(object): 2 | result = None 3 | 4 | def __init__(self, args): 5 | """ 6 | :param args: 7 | :type args:dict 8 | 9 | """ 10 | self.args = args 11 | self.result = None 12 | 13 | def calculate(self): 14 | """ 15 | 解析结果,要重写 16 | :return: 17 | """ 18 | pass 19 | 20 | def get_result(self): 21 | """ 22 | 23 | :return: 24 | :rtype: dict 25 | """ 26 | if not self.result: 27 | self.result = self.calculate() 28 | return self.result 29 | 30 | def __str__(self): 31 | return '%s:"%s"' % (self.__class__, self.args) 32 | 33 | 34 | def deeru_config_handler(name): 35 | def deco(cls): 36 | setattr(cls, 'deeru_config_handler_name', name) 37 | return cls 38 | 39 | return deco -------------------------------------------------------------------------------- /app/deeru_config_handler/handler.py: -------------------------------------------------------------------------------- 1 | from app.deeru_config_handler.base import BaseHandler, deeru_config_handler 2 | 3 | 4 | @deeru_config_handler('v2_kv_handler') 5 | class KVHandler(BaseHandler): 6 | """ 7 | args结构: 8 | { 9 | '_handler': 'v2_kv_handler', 10 | 'data':[ 11 | { 12 | 'key':'k1', 13 | 'value':'v1', 14 | }, 15 | ... 16 | ] 17 | } 18 | 19 | ========== 20 | 21 | 解析后的结构: 22 | { 23 | 'k1':'v1', 24 | 25 | ... 26 | } 27 | 28 | """ 29 | 30 | def calculate(self): 31 | """ 32 | 解析结果,要重写 33 | :return: 34 | :rtype: dict 35 | """ 36 | r = {} 37 | for i in self.args['data']: 38 | r[i['key']] = i['value'] 39 | return r 40 | 41 | 42 | @deeru_config_handler('v2_img_handler') 43 | class ImgHandler(BaseHandler): 44 | """ 45 | args结构: 46 | { 47 | '_handler': 'v2_img_handler', 48 | 'type': 'src', 49 | 'value': '/media/1.png' 50 | } 51 | 52 | ========== 53 | 54 | 解析后的结构: 55 | { 56 | 'type':'img', 57 | 'src':'/media/1.png', 58 | 59 | 60 | ... 61 | } 62 | 63 | """ 64 | 65 | def calculate(self): 66 | """ 67 | 解析结果,要重写 68 | :return: 69 | :rtype: dict 70 | """ 71 | from app.db_manager.other_manager import get_image_by_id,filter_image_by_start_name 72 | 73 | r = self.args 74 | v = self.args['value'].strip() 75 | type = self.args['type'] 76 | if type == 'src': 77 | r['type'] = 'img' 78 | r['src'] = v 79 | elif type == 'id': 80 | r['type'] = 'img' 81 | img = get_image_by_id(v) 82 | if img: 83 | r['src'] = img.img.url 84 | else: 85 | r['src'] = '' 86 | elif type == 'name': 87 | r['type'] = 'img' 88 | try: 89 | img = filter_image_by_start_name(v)[0] 90 | src = img.img.url 91 | except: 92 | src = '' 93 | r['src'] = src 94 | elif type == 'fa': 95 | r['type'] = 'fa' 96 | r['class'] = v 97 | elif type == 'svg': 98 | r['type'] = 'svg' 99 | r['svg'] = v 100 | return r 101 | 102 | 103 | @deeru_config_handler('v2_url_handler') 104 | class UrlHandler(BaseHandler): 105 | """ 106 | args结构: 107 | { 108 | '_handler': 'v2_url_handler', 109 | 'type': 'url', 110 | 'value': '/category/1' 111 | } 112 | 113 | ========== 114 | 115 | 解析后的结构: 116 | 117 | '/category/1' 118 | 119 | """ 120 | 121 | def calculate(self): 122 | """ 123 | 解析结果,要重写 124 | :return: 125 | :rtype: dict 126 | """ 127 | from app.db_manager.content_manager import get_category_by_id, filter_category_by_start_name, get_tag_by_id, \ 128 | filter_tag_by_start_name 129 | 130 | v = self.args['value'].strip() 131 | if self.args['type'] == 'url': 132 | return v 133 | elif self.args['type'] == 'cat': 134 | category = get_category_by_id(v) 135 | if not category: 136 | try: 137 | category = filter_category_by_start_name(v)[0] 138 | except: 139 | category = None 140 | if category: 141 | return category.url() 142 | else: 143 | return '' 144 | elif self.args['type'] == 'tag': 145 | tag = get_tag_by_id(v) 146 | if not tag: 147 | try: 148 | tag = filter_tag_by_start_name(v)[0] 149 | except: 150 | tag = None 151 | if tag: 152 | return tag.url() 153 | else: 154 | return '' 155 | -------------------------------------------------------------------------------- /app/deeru_config_handler/manager.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from app.manager.config_manager import get_global_value_by_key 4 | from django.conf import settings 5 | 6 | 7 | def parse_attrs(attrs_str): 8 | """ 9 | 对attrs进行特殊处理 10 | "style=xx | href=xx" 11 | 返回 12 | { 13 | 'style':'xx', 14 | 'href':'xx' 15 | } 16 | 17 | :param attrs_str: str 18 | :return: 19 | :rtype: dict 20 | """ 21 | attrs_str = attrs_str.strip() 22 | if not attrs_str: 23 | return {} 24 | attrs = {} 25 | for temp in attrs_str.strip().split('|'): 26 | k, v = temp.split('=') 27 | attrs[k.strip()] = v.strip() 28 | return attrs 29 | 30 | 31 | def format_config_handler(config): 32 | """ 33 | 34 | :param config: 35 | :type config: dict 36 | :return: 37 | :rtype: 38 | """ 39 | name = config.get('_handler', None) 40 | if not name: 41 | return config 42 | c = settings.CONFIG_HANDLER_DICT.get(name, None) 43 | if not c: 44 | return config 45 | return c(config).calculate() 46 | -------------------------------------------------------------------------------- /app/deeru_expression/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/deeru_expression/__init__.py -------------------------------------------------------------------------------- /app/deeru_expression/manager.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from app.manager.config_manager import get_global_value_by_key 4 | from django.conf import settings 5 | 6 | 7 | def format_expression(value): 8 | if not value or not isinstance(value, str): 9 | return value 10 | 11 | # 全局变量{{xxx}} 12 | expression = re.findall(r'^{{(.*)}}$', value.strip()) 13 | if expression: 14 | if not expression[0]: 15 | return None 16 | else: 17 | return get_global_value_by_key(expression[0]) 18 | # 表达式{%xx|xx%} 19 | expression = re.findall(r'^{%(.*)%}$', value.strip()) 20 | if expression: 21 | if not expression[0]: 22 | return None 23 | else: 24 | pos = expression[0].find('|') 25 | if pos == -1: 26 | return None 27 | else: 28 | exp_name = expression[0][:pos].strip() 29 | exp_args = expression[0][pos + 1:] 30 | exp_class = settings.EXPRESSION_DICT.get(exp_name.lower(), None) 31 | if not exp_class: 32 | return None 33 | return exp_class(exp_args) 34 | # 普通的字符串 35 | else: 36 | return value 37 | -------------------------------------------------------------------------------- /app/ex_admins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/ex_admins/__init__.py -------------------------------------------------------------------------------- /app/ex_admins/list_filter.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.utils.translation import ugettext_lazy as _ 3 | 4 | 5 | class CategoryFatherListFilter(admin.SimpleListFilter): 6 | # Human-readable title which will be displayed in the 7 | # right admin sidebar just above the filter options. 8 | title = _('所有分类') 9 | 10 | # Parameter for the filter that will be used in the URL query. 11 | parameter_name = 'father_id' 12 | 13 | def lookups(self, request, model_admin): 14 | """ 15 | Returns a list of tuples. The first element in each 16 | tuple is the coded value for the option that will 17 | appear in the URL query. The second element is the 18 | human-readable name for the option that will appear 19 | in the right sidebar. 20 | """ 21 | return ( 22 | ('-1', _('一级分类')), 23 | ) 24 | 25 | def queryset(self, request, queryset): 26 | """ 27 | Returns the filtered queryset based on the value 28 | provided in the query string and retrievable via 29 | `self.value()`. 30 | """ 31 | # Compare the requested value (either '80s' or '90s') 32 | # to decide how to filter the queryset. 33 | if self.value() == '-1': 34 | return queryset.filter(father_id=-1) 35 | 36 | def __init__(self, request, params, model, model_admin): 37 | super().__init__(request, params, model, model_admin) 38 | 39 | -------------------------------------------------------------------------------- /app/ex_fields/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/ex_fields/__init__.py -------------------------------------------------------------------------------- /app/ex_fields/fields.py: -------------------------------------------------------------------------------- 1 | import json 2 | from ast import literal_eval 3 | 4 | from django.db.models import TextField 5 | from django.forms import Textarea 6 | 7 | from jsonfield import JSONField 8 | 9 | from app.ex_fields.widgets import ConfigWidget, ConfigWidgetV2, DeerUFroalaEditor 10 | 11 | 12 | class ConfigField(TextField): 13 | 14 | def to_python(self, value): 15 | value = super().to_python(value) 16 | if not value: 17 | return value 18 | value = literal_eval(value) 19 | return json.dumps(value, ensure_ascii=False, indent=4) 20 | 21 | def formfield(self, **kwargs): 22 | defaults = {'widget': ConfigWidget, 23 | 'max_length': self.max_length} 24 | defaults.update(kwargs) 25 | defaults['widget'] = ConfigWidget 26 | return super(TextField, self).formfield(**defaults) 27 | 28 | 29 | class ConfigFieldV2(JSONField): 30 | 31 | def formfield(self, **kwargs): 32 | defaults = {'widget': ConfigWidgetV2} 33 | defaults.update(kwargs) 34 | defaults['widget'] = ConfigWidgetV2 35 | return super().formfield(**defaults) 36 | 37 | from froala_editor.fields import FroalaField 38 | 39 | class DeerUFroalaField(FroalaField): 40 | def formfield(self, **kwargs): 41 | if self.use_froala: 42 | widget = DeerUFroalaEditor(options=self.options, theme=self.theme, plugins=self.plugins, 43 | include_jquery=self.include_jquery, image_upload=self.image_upload, 44 | file_upload=self.file_upload, third_party=self.third_party) 45 | else: 46 | widget = Textarea() 47 | defaults = {'widget': widget} 48 | defaults.update(kwargs) 49 | return super(FroalaField, self).formfield(**defaults) 50 | -------------------------------------------------------------------------------- /app/ex_paginator.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import Paginator 2 | from django.utils.html import format_html 3 | 4 | 5 | class DeerUPaginator(Paginator): 6 | def __init__(self, object_list, per_page, current_page_num, orphans=0, allow_empty_first_page=True): 7 | super().__init__(object_list, per_page, orphans, allow_empty_first_page) 8 | self.current_page_num = current_page_num 9 | self.start_index = 1 10 | self.end_index = self.num_pages 11 | 12 | def get_page_list(self, number=None): 13 | """ 14 | :return: 15 | """ 16 | number = number or self.current_page_num 17 | result = [None, None, [], None, None] 18 | if number - 1 > 0: 19 | result[2].append(number - 1) 20 | result[1] = number - 1 21 | 22 | result[2].append(number) 23 | 24 | if number + 1 <= self.end_index: 25 | result[2].append(number + 1) 26 | result[3] = number + 1 27 | 28 | if len(result[2]) < 3 and number + 2 <= self.end_index: 29 | result[2].append(number + 2) 30 | if len(result[2]) < 3 and number - 2 >= 1: 31 | result[2] = [number - 2] + result[2] 32 | 33 | if number != 1: 34 | result[0] = 1 35 | if number != self.end_index: 36 | result[4] = self.end_index 37 | 38 | return result 39 | 40 | def get_page_html_list(self, number): 41 | """ 42 | 已过时 43 | :param number: 44 | :return: 45 | """ 46 | page = self.get_page_list(number) 47 | html1 = [{'text': format_html(''), 48 | 'disabled': '', 'is_current': ' ', 'href': ' '}, 49 | {'text': format_html(''), 50 | 'disabled': '', 'is_current': ' ', 'href': ' '}, 51 | {'text': format_html(''), 52 | 'disabled': '', 'is_current': ' ', 'href': ' '}, 53 | {'text': format_html(''), 54 | 'disabled': '', 'is_current': ' ', 'href': ' '}] 55 | i = 0 56 | for p in page[:2] + page[-2:]: 57 | if p: 58 | html1[i]['href'] = '?page=%d' % (p,) 59 | else: 60 | html1[i]['href'] = 'javascript:void(0)' 61 | html1[i]['disabled'] = 'disabled' 62 | i += 1 63 | 64 | html2 = [] 65 | for p in page[2]: 66 | html2.append({'text': '%d' % (p,), 'disabled': '', 67 | 'is_current': 'is-current' if p == number else ' ', 'href': '?page=%d' % (p,)}) 68 | 69 | return html1[:2] + html2 + html1[-2:] 70 | -------------------------------------------------------------------------------- /app/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/management/commands/__init__.py -------------------------------------------------------------------------------- /app/management/commands/init_deeru.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import traceback 4 | from getpass import getpass 5 | from pprint import pprint 6 | from django.core import management 7 | 8 | from django.contrib.auth.models import User, UserManager 9 | from django.core.management import BaseCommand 10 | from django.core.management.base import OutputWrapper, CommandError 11 | 12 | from app.app_models.config_model import Config 13 | from app.app_models.content_model import Article, Category, Tag, ArticleMeta, ArticleCategory, ArticleTag, Comment 14 | from app.manager.config_manager import cache_config 15 | from app.manager.ct_manager import update_one_to_many_relation_model 16 | from tool.datetime_helper import str_to_datetime, now 17 | 18 | 19 | class Command(BaseCommand): 20 | """ 21 | 初始化 22 | 23 | python manage.py init_deeru 24 | 25 | """ 26 | 27 | def handle(self, *args, **options): 28 | self.error = self.stderr.write 29 | 30 | info_out = OutputWrapper(sys.stdout) 31 | info_out.style_func = self.style.WARNING 32 | self.info = info_out.write 33 | 34 | success_out = OutputWrapper(sys.stdout) 35 | success_out.style_func = self.style.SUCCESS 36 | self.success = success_out.write 37 | 38 | self.info("初始化中:") 39 | try: 40 | os.mkdir('log') 41 | except: 42 | pass 43 | with open('./log/init.log', 'a', encoding='utf-8')as f: 44 | f.write('开始初始化(%s)\n' % str(now())) 45 | 46 | # ============ 47 | 48 | self.info('同步数据库修改 ... ') 49 | 50 | with open('./log/init.log', 'a', encoding='utf-8')as f: 51 | f.write('同步数据库修改\n') 52 | try: 53 | management.call_command('migrate', stdout=f) 54 | except: 55 | 56 | traceback.print_exc(file=f) 57 | self.error('同步数据库修改 ... [失败],更多信息查看 ./log/init.log ') 58 | raise 59 | 60 | self.success('同步数据库修改 ... [完成]') 61 | 62 | # ============ 63 | 64 | self.info('初始化静态文件 ... ') 65 | with open('./log/init.log', 'a', encoding='utf-8')as f: 66 | f.write('初始化静态文件\n') 67 | try: 68 | management.call_command('collectstatic', '-c', '--noinput', stdout=f) 69 | except: 70 | traceback.print_exc(file=f) 71 | self.error('初始化静态文件 ... [失败],更多信息查看 ./log/init.log ') 72 | raise 73 | 74 | self.success('初始化静态文件 ... [完成]') 75 | 76 | # ============ 77 | 78 | self.info('创建管理员账户 ... ') 79 | is_create_user = 'y' 80 | if User.objects.count() > 0: 81 | is_create_user = input('已存在管理员账户,是否创建新的管理员( y/n,默认:n )') 82 | if not is_create_user: 83 | is_create_user = 'n' 84 | 85 | if is_create_user == 'y': 86 | username = input('输入管理账户登录名(默认:admin):') 87 | if not username: 88 | username = 'admin' 89 | password = getpass('输入密码(默认:123456):') 90 | if not password: 91 | password = '123456' 92 | with open('./log/init.log', 'a', encoding='utf-8')as f: 93 | try: 94 | flag = True 95 | User._default_manager.db_manager('default').create_superuser(username, None, password) 96 | self.success('创建管理员账户 ... [完成]') 97 | except: 98 | flag = False 99 | traceback.print_exc(3) 100 | 101 | traceback.print_exc(file=f) 102 | self.error('创建管理员账户 ... [失败],更多信息查看 ./log/init.log ') 103 | 104 | 105 | finally: 106 | if not flag: 107 | return 108 | else: 109 | self.info('跳过创建管理员') 110 | 111 | self.success('\n初始化完成 !!') 112 | -------------------------------------------------------------------------------- /app/management/commands/temp.py: -------------------------------------------------------------------------------- 1 | from django.core.management import BaseCommand 2 | 3 | from tool.version_upgrade.v1_config_to_v2 import config_v1_to_v2 4 | 5 | 6 | class Command(BaseCommand): 7 | """ 8 | 用来临时跑一些东西 9 | python manage.py temp 10 | """ 11 | 12 | def handle(self, *args, **options): 13 | config_v1_to_v2() 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/manager/__init__.py: -------------------------------------------------------------------------------- 1 | def get_config_context(): 2 | from django.apps import apps 3 | import importlib 4 | from app.db_manager.config_manager import get_config_by_name 5 | 6 | result = {} 7 | 8 | for app in apps.get_app_configs(): 9 | deeru_config = getattr(app, 'deeru_config_context', None) 10 | if deeru_config: 11 | deeru_config = deeru_config.split('.') 12 | module_name = '.'.join(deeru_config[:-1]) 13 | consts = importlib.import_module(module_name) 14 | app_config = getattr(consts, deeru_config[-1], {}) 15 | for k, v in app_config.items(): 16 | config = get_config_by_name(v) 17 | if config: 18 | result[k] = config.v2_real_config 19 | return result 20 | 21 | 22 | def get_base_context(context): 23 | from app.manager.uiconfig_manager import get_aside_category2, get_aside_tags 24 | context['config'] = get_config_context() 25 | 26 | context['global'] = {} 27 | context['global']['category'] = get_aside_category2() 28 | context['global']['tags'] = get_aside_tags() 29 | 30 | return context 31 | -------------------------------------------------------------------------------- /app/manager/article.py: -------------------------------------------------------------------------------- 1 | from app.db_manager.content_manager import all_article_order_by_id, filter_article_by_category, filter_article_by_tag 2 | from app.ex_paginator import DeerUPaginator 3 | 4 | 5 | def get_article_list_and_paginator(page, per_page): 6 | paginator = DeerUPaginator(all_article_order_by_id(), per_page, page) 7 | 8 | article_list = paginator.page(page).object_list 9 | return article_list, paginator 10 | 11 | 12 | def get_article_list_and_paginator_by_category(category_id, page, per_page): 13 | paginator = DeerUPaginator(filter_article_by_category(category_id), per_page, page) 14 | 15 | article_list = paginator.page(page).object_list 16 | return article_list, paginator 17 | 18 | 19 | def get_article_list_and_paginator_by_tag(tag_id, page, per_page): 20 | paginator = DeerUPaginator(filter_article_by_tag(tag_id), per_page, page) 21 | 22 | article_list = paginator.page(page).object_list 23 | return article_list, paginator 24 | -------------------------------------------------------------------------------- /app/manager/config_manager.py: -------------------------------------------------------------------------------- 1 | from django.core.cache import cache 2 | 3 | from app.consts import app_config_context, Global_Value_Default, Global_value_cache_key, Theme_config_cache_key, \ 4 | Theme_cache_key, v2_app_config_context 5 | from app.db_manager.config_manager import get_config_by_name 6 | from ast import literal_eval 7 | 8 | 9 | def get_global_value_by_key(name): 10 | global_value = get_global_value() 11 | 12 | default = Global_Value_Default.get(name, '') 13 | 14 | return global_value.get(name, default) 15 | 16 | 17 | def get_global_value(): 18 | try: 19 | global_value = cache.get(Global_value_cache_key, None) 20 | assert global_value is not None 21 | except: 22 | config = get_config_by_name(app_config_context['global_value']) 23 | global_value = literal_eval(config.cache) 24 | cache.set(Global_value_cache_key, global_value, 3600) 25 | 26 | return global_value 27 | 28 | 29 | def get_theme_config(): 30 | try: 31 | theme_config = cache.get(Theme_config_cache_key, None) 32 | assert theme_config is not None 33 | except: 34 | config = get_config_by_name(app_config_context['common_config']) 35 | theme_config = literal_eval(config.config) 36 | cache.set(Theme_config_cache_key, theme_config, 3600) 37 | 38 | return theme_config 39 | 40 | 41 | def get_theme(): 42 | try: 43 | theme = cache.get(Theme_cache_key, None) 44 | assert theme is not None 45 | 46 | except: 47 | config = get_config_by_name(v2_app_config_context['v2_blog_config']) 48 | try: 49 | theme = config.v2_real_config['theme'].strip() 50 | except: 51 | theme = 'base_theme' 52 | if not theme: 53 | theme = 'base_theme' 54 | cache.set(Theme_config_cache_key, theme, 3600) 55 | 56 | return theme 57 | 58 | 59 | from app.deeru_expression.expressions import BaseExpression 60 | from app.deeru_expression.manager import format_expression 61 | 62 | 63 | def cache_config(config, is_init=False): 64 | """ 65 | 66 | :param config: Config object 67 | :return: 68 | """ 69 | 70 | temp_config = literal_eval(config.config) 71 | 72 | result = get_config_cache(temp_config) 73 | 74 | config.cache = str(result) 75 | if not is_init: 76 | config.set_post_save_flag(False) 77 | config.save() 78 | 79 | 80 | def get_expression_result(s): 81 | if not s: 82 | return s 83 | result = format_expression(s) 84 | if isinstance(result, BaseExpression): 85 | result = result.get_result() 86 | return result 87 | 88 | 89 | def get_config_cache(config): 90 | if isinstance(config, list): 91 | result = [] 92 | for item in config: 93 | item_cached = get_config_cache(item) 94 | result.append(item_cached) 95 | elif isinstance(config, dict): 96 | result = {} 97 | for k, v in config.items(): 98 | v_cached = get_config_cache(v) 99 | result[k] = v_cached 100 | elif isinstance(config, str): 101 | result = get_expression_result(config) 102 | else: 103 | result = config 104 | return result 105 | -------------------------------------------------------------------------------- /app/manager/config_manager_v2.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | from app.deeru_config_handler.manager import parse_attrs, format_config_handler 4 | 5 | 6 | def get_real_config(real_config): 7 | """ 8 | 9 | :param real_config: 10 | :type real_config: dict 11 | :return: 12 | :rtype: dict 13 | """ 14 | if not real_config: 15 | return {} 16 | r = deepcopy(real_config) 17 | for k, v in real_config.items(): 18 | if isinstance(v, dict): 19 | v = get_real_config(v) 20 | 21 | r[k] = v 22 | if isinstance(v, list): 23 | r[k] = parse_list_config(v) 24 | # todo 后面有时间替换为用handler处理 25 | if k == '_attrs': 26 | r['attrs'] = parse_attrs(v) 27 | if '_handler' in r: 28 | r = format_config_handler(r) 29 | return r 30 | 31 | 32 | def parse_list_config(l): 33 | """ 34 | 35 | :param l: 36 | :type l: list 37 | :return: 38 | :rtype: list 39 | """ 40 | r = deepcopy(l) 41 | for i, item in enumerate(l): 42 | if isinstance(item, dict): 43 | r[i] = get_real_config(item) 44 | return r 45 | -------------------------------------------------------------------------------- /app/manager/content_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from cache_utils.decorators import cached 3 | from django.conf import settings 4 | from django.template.loader import render_to_string 5 | 6 | from app.consts import FLAT_PAGE_URL_CACHE_KEY, v2_app_config_context 7 | from app.db_manager.config_manager import get_config_by_name 8 | from app.db_manager.content_manager import all_flatpage, get_article_by_id, get_valid_comment_by_id_and_article, \ 9 | get_comment_by_id 10 | from app.manager.email import send_mail 11 | 12 | 13 | @cached(86400, key=FLAT_PAGE_URL_CACHE_KEY) 14 | def get_flatpage_url_dict(): 15 | pages = all_flatpage().values_list('url', 'id') 16 | urld = {} 17 | for k, v in pages: 18 | urld[k] = v 19 | return urld 20 | 21 | 22 | def is_valid_comment(comment_form): 23 | to_id = comment_form.cleaned_data['to_id'] 24 | article_id = comment_form.cleaned_data['article_id'] 25 | root_id = comment_form.cleaned_data['root_id'] 26 | type = comment_form.cleaned_data['type'] 27 | 28 | article = get_article_by_id(article_id) 29 | if not article: 30 | return False, '错误id' 31 | 32 | if type == 202: 33 | to_comment = get_valid_comment_by_id_and_article(to_id, article_id) 34 | if not to_comment: 35 | return False, '错误to_id' 36 | 37 | if to_comment.root_id == -1: 38 | if root_id != to_id: 39 | return False, '错误to_id' 40 | else: 41 | if to_comment.root_id != root_id: 42 | return False, '错误root_id' 43 | 44 | return True, '' 45 | 46 | 47 | def send_reply_email(comment): 48 | """ 49 | 50 | :param comment: 51 | :type comment: app.app_models.content_model.Comment 52 | :return: 53 | :rtype: 54 | """ 55 | 56 | if comment.type != 202: 57 | return 58 | 59 | to_comment = get_comment_by_id(comment.to_id) 60 | if not to_comment or not to_comment.email: 61 | return 62 | 63 | blog_config = get_config_by_name(v2_app_config_context['v2_blog_config']).v2_real_config 64 | blog_name = blog_config.get('blog_name', '') 65 | article = comment.article() 66 | comment_url = article.url() + '#comment-' + str(comment.id) 67 | email = blog_config['email'] 68 | send_mail( 69 | '你在《%s》下的评论有新回复 (来自于:%s)' % (article.title, blog_name), 70 | '', 71 | [to_comment.email], 72 | email_config=email, 73 | html_message=render_to_string('app/email/comment_email.html', 74 | {'nickname': blog_config.get('nickname', ''), 'article': article, 75 | 'comment': comment, 'comment_url': comment_url, 76 | 'host': blog_config['host']}) 77 | ) 78 | -------------------------------------------------------------------------------- /app/manager/email.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from django.conf import settings 4 | from django.core.mail import EmailMultiAlternatives, get_connection 5 | from django.core.mail.backends.smtp import EmailBackend 6 | 7 | from app.consts import v2_app_config_context 8 | from app.db_manager.config_manager import get_config_by_name 9 | from tool.secure import descrypt 10 | from tool.sign import unsign 11 | 12 | 13 | def _send(subject, message, recipient_list, 14 | email_config=None, html_message=None, fail_silently=False): 15 | if not email_config: 16 | blog_config = get_config_by_name(v2_app_config_context['v2_blog_config']).v2_real_config 17 | email_config = blog_config['email'] 18 | 19 | username = email_config.get('username', None) or settings.EMAIL_HOST_USER 20 | password = email_config.get('password', None) 21 | if password: 22 | password = descrypt(unsign(password)) 23 | else: 24 | password = settings.EMAIL_HOST_PASSWORD 25 | smtp = email_config.get('smtp', None) or settings.EMAIL_HOST 26 | port = email_config.get('port', None) or settings.EMAIL_PORT 27 | secure = email_config.get('secure', None) 28 | if not secure: 29 | if settings.EMAIL_USE_TLS: 30 | secure = 'tls' 31 | elif settings.EMAIL_USE_SSL: 32 | secure = 'ssl' 33 | if not username or not password or not smtp or not port: 34 | return 35 | 36 | kwargs = { 37 | 'host': smtp, 38 | 'port': port, 39 | 'username': username, 40 | 'password': password, 41 | 'fail_silently': fail_silently, 42 | 'timeout': 10, 43 | 44 | } 45 | if secure == 'tls': 46 | kwargs['use_tls'] = True 47 | elif secure == 'ssl': 48 | kwargs['use_ssl'] = True 49 | 50 | connection = EmailBackend(**kwargs) 51 | mail = EmailMultiAlternatives(subject, message, username, recipient_list, connection=connection) 52 | if html_message: 53 | mail.attach_alternative(html_message, 'text/html') 54 | 55 | a = mail.send() 56 | 57 | 58 | def send_mail(subject, message, recipient_list, 59 | email_config=None, html_message=None, fail_silently=False): 60 | t = threading.Thread(target=_send, 61 | args=(subject, message, recipient_list, email_config, html_message, fail_silently)) 62 | t.start() 63 | -------------------------------------------------------------------------------- /app/manager/expression_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | __author__ = 'lnx' -------------------------------------------------------------------------------- /app/manager/theme.py: -------------------------------------------------------------------------------- 1 | from app.forms import CommentForm 2 | from app.manager import get_config_context 3 | from app.manager.article import get_article_list_and_paginator, get_article_list_and_paginator_by_category, \ 4 | get_article_list_and_paginator_by_tag 5 | from base_theme2.theme import HomeTemplate, DetailArticleTemplate, DetailFlatpageTemplate, Page404Template 6 | 7 | 8 | def get_extend_data(): 9 | from app.manager.uiconfig_manager import get_aside_category2, get_aside_tags 10 | return { 11 | 'category': get_aside_category2(), 12 | 'tags': get_aside_tags() 13 | } 14 | 15 | 16 | def get_home_template(page, per_page=7): 17 | article_list, paginator = get_article_list_and_paginator(page, per_page) 18 | return HomeTemplate(get_config_context(), article_list, paginator, None, get_extend_data()) 19 | 20 | 21 | def get_category_template(category, page, per_page=7): 22 | article_list, paginator = get_article_list_and_paginator_by_category(category.id, page, per_page) 23 | return HomeTemplate(get_config_context(), article_list, paginator, ['分类', category.name], get_extend_data()) 24 | 25 | 26 | def get_tag_template(tag, page, per_page=7): 27 | article_list, paginator = get_article_list_and_paginator_by_tag(tag.id, page, per_page) 28 | return HomeTemplate(get_config_context(), article_list, paginator, ['标签', tag.name], get_extend_data()) 29 | 30 | 31 | def get_detail_article_template(article, form_error): 32 | comment_form = CommentForm() 33 | return DetailArticleTemplate(get_config_context(), article, comment_form, form_error, get_extend_data()) 34 | 35 | 36 | def get_detail_flatpage_template(flatpage): 37 | return DetailFlatpageTemplate(get_config_context(), flatpage, get_extend_data()) 38 | 39 | 40 | def get_404_template(): 41 | return Page404Template(get_config_context(), get_extend_data()) 42 | -------------------------------------------------------------------------------- /app/manager/uiconfig_manager.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Count 2 | 3 | from app.app_models.content_model import Tag, ArticleTag 4 | from app.consts import app_config_context 5 | from app.db_manager.config_manager import get_config_by_name 6 | from app.db_manager.content_manager import get_tag_by_id 7 | from app.manager.ct_manager import get_category_tree, get_category_tree2 8 | from tool.deeru_exceptions import ConfigNotExistError 9 | from ast import literal_eval 10 | 11 | 12 | def get_aside_category2(): 13 | return get_category_tree2() 14 | 15 | 16 | def get_aside_tags(): 17 | article_tag = ArticleTag.objects.values('tag_id').annotate(article_num=Count('id')).order_by('-article_num')[:20] 18 | aside_tags = [] 19 | for at in article_tag: 20 | tag = get_tag_by_id(at['tag_id']) 21 | if tag: 22 | aside_tags.append([tag, at['article_num']]) 23 | fill_tags = [] 24 | if len(aside_tags) != 20: 25 | fill_tags = Tag.objects.exclude(id__in=[at['tag_id'] for at in article_tag])[:20 - len(article_tag)] 26 | 27 | fill_tags = [[t, 0] for t in fill_tags] 28 | 29 | return aside_tags + fill_tags 30 | -------------------------------------------------------------------------------- /app/migrations/0002_auto_20180716_1327.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.3 on 2018-05-25 09:56 2 | 3 | import os 4 | 5 | from django.conf import settings 6 | from django.core.files.images import ImageFile 7 | from django.db import migrations 8 | 9 | from app.consts import app_config_context, v2_app_config_context, V2_Config_Schema, V2_Config_JS 10 | from app.manager.config_manager import cache_config 11 | from app.manager.ct_manager import update_one_to_many_relation_model 12 | 13 | 14 | def upload_img(model, name, path): 15 | with open(path, 'rb')as f: 16 | img = ImageFile(f) 17 | a = model(name=name) 18 | a.img.save(name, img) 19 | 20 | 21 | def init_img(apps, schema_editor): 22 | Album = apps.get_model("app", "Album") 23 | base_dir = settings.BASE_DIR 24 | upload_img(Album, 'logo_white.png', os.path.join(base_dir, 'logo_white.png')) 25 | upload_img(Album, 'logo_black.png', os.path.join(base_dir, 'logo_black.png')) 26 | 27 | upload_img(Album, 'deeru_green.png', os.path.join(base_dir, 'deeru_green.png')) 28 | 29 | 30 | def init_content(apps, schema_editor): 31 | Category = apps.get_model("app", "Category") 32 | c = Category.objects.create(name='默认分类') 33 | 34 | Tag = apps.get_model("app", "Tag") 35 | t = Tag.objects.create(name='DeerU') 36 | 37 | Album = apps.get_model("app", "Album") 38 | logo_black = Album.objects.get(name='logo_black.png') 39 | deeru_green = Album.objects.get(name='deeru_green.png') 40 | 41 | Article = apps.get_model("app", "Article") 42 | a = Article.objects.create(title='欢迎使用DeerU', 43 | content='




感谢你使用DeerU😀  

DeerU是一个免费开源的博客系统


如果你有什么问题或者建议欢迎联系反馈给我


文档:https://deeru.readthedocs.io

GITHUB: https://github.com/gojuukaze/DeerU

' % logo_black.img.url, 44 | summary='欢迎你使用DeerU😀
DeerU是一个免费开源的博客系统
如果你有什么问题或者建议欢迎联系反馈给我
...', 45 | image=deeru_green.img.url 46 | ) 47 | ArticleMeta = apps.get_model("app", "ArticleMeta") 48 | ArticleMeta.objects.create(article_id=a.id) 49 | 50 | ArticleCategory = apps.get_model("app", "ArticleCategory") 51 | update_one_to_many_relation_model(ArticleCategory, 'article_id', a.id, 'category_id', [c.id], 52 | lambda x: x, []) 53 | ArticleTag = apps.get_model("app", "ArticleTag") 54 | update_one_to_many_relation_model(ArticleTag, 'article_id', a.id, 'tag_id', [t.id], 55 | lambda x: x, []) 56 | 57 | 58 | class Migration(migrations.Migration): 59 | dependencies = [ 60 | ('app', '0001_initial'), 61 | ] 62 | 63 | operations = [ 64 | migrations.RunPython(init_img), 65 | migrations.RunPython(init_content), 66 | ] 67 | -------------------------------------------------------------------------------- /app/migrations/0003_auto_20191207_1737.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.8 on 2019-12-07 17:37 2 | 3 | import app.ex_fields.fields 4 | from django.db import migrations, models 5 | import jsonfield.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('app', '0002_auto_20180716_1327'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Version', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('version', models.CharField(editable=False, max_length=20, verbose_name='版本号')), 20 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 21 | ('modified_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 22 | ], 23 | options={ 24 | 'verbose_name': '版本', 25 | 'verbose_name_plural': '版本', 26 | }, 27 | ), 28 | migrations.AddField( 29 | model_name='comment', 30 | name='status', 31 | field=models.IntegerField(choices=[(0, '待审核'), (1, '未通过'), (2, '通过')], default=0, verbose_name='状态'), 32 | ), 33 | migrations.AddField( 34 | model_name='config', 35 | name='v2_config', 36 | field=app.ex_fields.fields.ConfigFieldV2(blank=True, null=True, verbose_name='v2版配置'), 37 | ), 38 | migrations.AddField( 39 | model_name='config', 40 | name='v2_real_config', 41 | field=jsonfield.fields.JSONField(blank=True, editable=False, null=True, verbose_name='解析后的config'), 42 | ), 43 | migrations.AddField( 44 | model_name='config', 45 | name='v2_schema', 46 | field=models.TextField(blank=True, null=True, verbose_name='json-editor配置'), 47 | ), 48 | migrations.AddField( 49 | model_name='config', 50 | name='v2_script', 51 | field=models.TextField(default='', verbose_name='js代码'), 52 | ), 53 | migrations.AlterField( 54 | model_name='config', 55 | name='config', 56 | field=app.ex_fields.fields.ConfigField(null=True, verbose_name='v1版配置'), 57 | ), 58 | migrations.AlterField( 59 | model_name='config', 60 | name='last_config', 61 | field=app.ex_fields.fields.ConfigField(blank=True, null=True, verbose_name='v1版旧配置'), 62 | ), 63 | migrations.AlterField( 64 | model_name='config', 65 | name='name', 66 | field=models.CharField(max_length=20, unique=True, verbose_name='配置名称'), 67 | ), 68 | ] 69 | -------------------------------------------------------------------------------- /app/migrations/0004_auto_20191207_1737.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.8 on 2019-12-07 17:37 2 | 3 | from django.db import migrations 4 | 5 | from app.consts import v2_app_config_context, V2_Config_Schema, V2_Config_JS 6 | from app.manager.config_manager_v2 import get_real_config 7 | from tool.version_upgrade.v1_config_to_v2 import config_v1_to_v2 8 | 9 | 10 | def init_version(apps, schema_editor): 11 | Version = apps.get_model("app", "Version") 12 | Version.objects.create(version='v2.0.0') 13 | 14 | 15 | def init_config(Config): 16 | 17 | Config.objects.bulk_create([ 18 | # 通用配置 19 | Config(name=v2_app_config_context['v2_common_config'], v2_schema=V2_Config_Schema['v2_common_config'], 20 | v2_config={"_handler": "v2_kv_handler", "data": []}), 21 | # 顶部图标栏 22 | Config(name=v2_app_config_context['v2_iconbar_config'], v2_schema=V2_Config_Schema['v2_iconbar_config'], 23 | v2_config={ 24 | "left": {"logo": {"_handler": "v2_img_handler", "type": "name", "value": "logo_white", "_attrs": ""}, 25 | "blog_name": {"text": " 文字标题 ", "_attrs": "style=font-size:18px"}}, "right": [{"img": { 26 | "_handler": "v2_img_handler", "type": "fa", "value": "fab fa-github", 27 | "_attrs": "style=color:#ffffff;font-size:24px"}, "url": "https://github.com/gojuukaze/DeerU"}]}), 28 | # 顶部导航栏 29 | Config(name=v2_app_config_context['v2_navbar_config'], v2_schema=V2_Config_Schema['v2_navbar_config'], 30 | v2_script=V2_Config_JS['v2_navbar_config'], 31 | v2_config={"menu": [{"name": "首页", "url": {"_handler": "v2_url_handler", "type": "url", "value": "/"}, 32 | "img": {"_handler": "v2_img_handler", "type": "fa", "value": "fas fa-home ", 33 | "_attrs": ""}, "children": []}, 34 | {"name": "折叠菜单", "url": {"_handler": "v2_url_handler", "type": "url", "value": ""}, 35 | "img": {"_handler": "v2_img_handler", "type": "fa", "value": "fas fa-list ", 36 | "_attrs": ""}, "children": [{"name": "默认分类", 37 | "url": {"_handler": "v2_url_handler", 38 | "type": "cat", "value": "默认分类"}, 39 | "img": {" ": ""}}, {"line": "line"}, 40 | {"name": "DeerU", 41 | "url": {"_handler": "v2_url_handler", 42 | "type": "tag", "value": "DeerU"}, 43 | "img": {" ": ""}}]}]}), 44 | 45 | # 博客配置 46 | Config(name=v2_app_config_context['v2_blog_config'], v2_schema=V2_Config_Schema['v2_blog_config'], 47 | v2_config={"host": "http://127.0.0.1:8000", "title": "Deeru - 开源博客系统", "blog_name": "Deeru - 开源博客系统", 48 | "nickname": "gojuukaze", "theme": "base_theme", "baidu_auto_push_show": "是", 49 | "baidu_auto_push": 1, "email": {"is_open": False}}), 50 | 51 | 52 | ]) 53 | for config in Config.objects.all(): 54 | config.v2_real_config = get_real_config(config.v2_config) 55 | config.save() 56 | 57 | 58 | def upgrade_config(apps, schema_editor): 59 | Config = apps.get_model("app", "Config") 60 | try: 61 | common_config = Config.objects.get(name=v2_app_config_context['v2_common_config']) 62 | except: 63 | common_config = None 64 | 65 | 66 | if not common_config: 67 | init_config(Config) 68 | elif not common_config.v2_config: 69 | config_v1_to_v2(Config) 70 | print('v2配置已更新,需要进入配置更新"博客配置"') 71 | else: 72 | print('配置初始化未成功') 73 | 74 | 75 | class Migration(migrations.Migration): 76 | dependencies = [ 77 | ('app', '0003_auto_20191207_1737'), 78 | ] 79 | 80 | operations = [ 81 | migrations.RunPython(init_version), 82 | migrations.RunPython(upgrade_config), 83 | ] 84 | -------------------------------------------------------------------------------- /app/migrations/0005_article_sorted_num.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.9 on 2020-12-29 15:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('app', '0004_auto_20191207_1737'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='article', 15 | name='sorted_num', 16 | field=models.IntegerField(default=0, verbose_name='排序号'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/migrations/__init__.py -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from app.app_models.config_model import * 2 | from app.app_models.content_model import * 3 | from app.app_models.other_model import * 4 | -------------------------------------------------------------------------------- /app/signals/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/signals/__init__.py -------------------------------------------------------------------------------- /app/signals/handlers.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from bs4 import BeautifulSoup 4 | from django.dispatch import receiver 5 | from django.db.models.signals import post_save, pre_save, post_delete 6 | 7 | from app.app_models.config_model import Config 8 | from app.app_models.content_model import Article, Comment, FlatPage 9 | from app.app_models.other_model import Album 10 | from app.db_manager.content_manager import get_or_create_article_meta, get_article_meta_by_article, \ 11 | filter_valid_comment_by_article 12 | from app.manager.config_manager import cache_config 13 | from app.manager.config_manager_v2 import get_real_config 14 | from app.manager.content_manager import get_flatpage_url_dict, send_reply_email 15 | 16 | 17 | @receiver(pre_save, sender=Article, dispatch_uid="article_pre_save") 18 | def article_pre_save(sender, **kwargs): 19 | cover_img = getattr(kwargs['instance'], 'cover_img', None) 20 | cover_summary = getattr(kwargs['instance'], 'cover_summary', None) 21 | soup = BeautifulSoup(kwargs['instance'].content) 22 | 23 | if cover_img: 24 | kwargs['instance'].image = cover_img 25 | else: 26 | img = soup.find('img') 27 | if img: 28 | kwargs['instance'].image = img['src'] 29 | if cover_summary: 30 | kwargs['instance'].summary = cover_summary + "..." 31 | else: 32 | 33 | kwargs['instance'].summary = soup.get_text()[:170] + "..." 34 | 35 | 36 | @receiver(post_save, sender=Article, dispatch_uid="article_post_save") 37 | def article_post_save(sender, **kwargs): 38 | get_or_create_article_meta(kwargs['instance'].id) 39 | 40 | 41 | @receiver(post_save, sender=Comment, dispatch_uid="comment_post_save") 42 | def comment_post_save(sender, **kwargs): 43 | a_meta = get_article_meta_by_article(kwargs['instance'].article_id) 44 | a_meta.comment_num = filter_valid_comment_by_article(kwargs['instance'].article_id).filter(type=201).count() 45 | a_meta.save() 46 | send_reply_email(kwargs['instance']) 47 | 48 | 49 | @receiver(post_delete, sender=Comment, dispatch_uid="comment_post_delete") 50 | def comment_post_delete(sender, **kwargs): 51 | a_meta = get_article_meta_by_article(kwargs['instance'].article_id) 52 | a_meta.comment_num = filter_valid_comment_by_article(kwargs['instance'].article_id).filter(type=201).count() 53 | a_meta.save() 54 | 55 | @receiver(pre_save, sender=Album, dispatch_uid="album_pre_save") 56 | def album_pre_save(sender, **kwargs): 57 | if not kwargs['instance'].name: 58 | kwargs['instance'].name = kwargs['instance'].img.name 59 | 60 | 61 | @receiver(post_save, sender=FlatPage, dispatch_uid="flatpage_post_save") 62 | def flatpage_post_save(sender, **kwargs): 63 | get_flatpage_url_dict.invalidate() 64 | 65 | 66 | @receiver(pre_save, sender=Config, dispatch_uid="config_pre_save") 67 | def config_pre_save(sender, **kwargs): 68 | if not kwargs['instance'].name.endswith('.old'): 69 | kwargs['instance'].v2_real_config = get_real_config(kwargs['instance'].v2_config) 70 | -------------------------------------------------------------------------------- /app/sitemap.py: -------------------------------------------------------------------------------- 1 | from app.db_manager.content_manager import all_article_order_by_id, get_all_category 2 | 3 | article_dict = { 4 | 'queryset': all_article_order_by_id(), 5 | 'date_field': 'modified_time', 6 | } 7 | -------------------------------------------------------------------------------- /app/templates/app/admin/article_change_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/change_form.html' %} 2 | {% load static %} 3 | 4 | {% block admin_change_form_document_ready %} 5 | {{ block.super }} 6 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /app/templates/app/admin/article_title.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {{ article.title }} 6 | 20 |
21 | -------------------------------------------------------------------------------- /app/templates/app/admin/category_change_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'adminsortable2/change_list.html' %} 2 | {% block object-tools-items %} 3 | {{ block.super }} 4 | {#
  • #} 5 | {# My Link#} 6 | {#
  • #} 7 | 25 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/app/admin/comment_author.html: -------------------------------------------------------------------------------- 1 |
    2 |

    id - {{ id }}

    3 |
    4 | 5 |
    6 |

    {{ nickname }}

    7 | {{ email }} 8 |
    9 |
    10 | 11 |
    12 | -------------------------------------------------------------------------------- /app/templates/app/admin/comment_content.html: -------------------------------------------------------------------------------- 1 |
    2 |

    {{ comment.content|safe }}

    3 | 9 |
    10 | -------------------------------------------------------------------------------- /app/templates/app/admin/comment_status.html: -------------------------------------------------------------------------------- 1 | 9 | {% if status == 0 %} 10 |
    11 | 12 | 14 | 15 | 待审核 16 |
    17 | {% elif status == 1 %} 18 |
    19 | 20 | 22 | 23 | 不通过 24 |
    25 | {% elif status == 2 %} 26 |
    27 | 28 | 30 | 31 | 通过 32 |
    33 | {% else %} 34 |
    未知
    35 | {% endif %} -------------------------------------------------------------------------------- /app/templates/app/admin/config.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 74 | 75 | 76 |
    77 | 93 | {% if script %} 94 | 97 | {% endif %} 98 | 99 | 100 | -------------------------------------------------------------------------------- /app/templates/app/admin/flatpage_title.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | {{ flatpage.title }} 6 | 13 |
    14 | -------------------------------------------------------------------------------- /app/templates/app/email/comment_email.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | {{ comment.nickname }} 5 |
    6 | 7 | 发布于:{{ comment.created_time }} 8 |
    9 | {{ comment.content|safe }} 10 |
    11 |
    12 |
    13 |
    14 |

    {{ blog_name }}

    15 |

    文章:{{ article.title }}

    16 |

    链接:{{ host }}{{ article.url }}

    17 |

    作者:{{ nickname }}

    18 |
    19 | -------------------------------------------------------------------------------- /app/tests.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.test import TestCase 3 | 4 | # Create your tests here. 5 | -------------------------------------------------------------------------------- /app/urls/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import re 3 | 4 | from django.conf import settings 5 | from django.urls import path, include, re_path 6 | 7 | app_name = 'app' 8 | 9 | urlpatterns = [ 10 | path('', include('app.urls.urls')), 11 | path(settings.FLATPAGE_URL.lstrip('/').strip('/'), include('app.urls.flatpage_url')), 12 | 13 | ] 14 | -------------------------------------------------------------------------------- /app/urls/flatpage_url.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from django.urls import path 4 | 5 | from app.views import views_class, views_v2 6 | 7 | urlpatterns = [ 8 | path('', views_v2.detail_flatpage_view, name='detail_flatpage'), 9 | 10 | ] 11 | -------------------------------------------------------------------------------- /app/urls/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.urls import path 3 | from django.contrib.sitemaps.views import sitemap 4 | from django.contrib.sitemaps import GenericSitemap 5 | 6 | from app.sitemap import article_dict 7 | from app.views import views_class 8 | from app.views import views 9 | from app.views import views_v2 10 | 11 | urlpatterns = [ 12 | path('', views_v2.home_view, name='index'), 13 | path('article/', views_v2.detail_article_view, name='detail_article'), 14 | path('article/set_top/', views.set_article_top_view, name='set_article_top'), 15 | 16 | path('category/', views_v2.category_article_list_view, name='category_article'), 17 | path('tag/', views_v2.tag_article_list_view, name='tag_article'), 18 | path('comment/create', views.create_comment_view, name='create_comment'), 19 | 20 | path('image/upload', views.upload_image_view, name='upload_image'), 21 | path('images', views.get_album_view, name='get_album'), 22 | path('image/delete', views.delete_image_view, name='delete_image'), 23 | path('sitemap.xml', sitemap, {'sitemaps': 24 | {'article': GenericSitemap(article_dict, priority=0.6), }, 25 | }, 26 | name='django.contrib.sitemaps.views.sitemap'), 27 | # path('p/', views_class.DetailFlatPage.as_view(), name='detail_flatpage'), 28 | 29 | path('404', views.page_not_found_view), 30 | 31 | path('config//html', views.get_config_html_view), 32 | 33 | ] 34 | -------------------------------------------------------------------------------- /app/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/app/views/__init__.py -------------------------------------------------------------------------------- /app/views/views_v2.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse, Http404 2 | 3 | from app.db_manager.content_manager import get_article_by_id, get_category_by_id, \ 4 | get_tag_by_id, get_flatpage_by_id 5 | 6 | from app.manager.content_manager import get_flatpage_url_dict 7 | from app.manager.theme import get_home_template, get_category_template, get_tag_template, get_detail_article_template, \ 8 | get_detail_flatpage_template, get_404_template 9 | 10 | 11 | def home_view(request): 12 | """ 13 | """ 14 | page = int(request.GET.get('page', 1)) 15 | # per_page = int(request.GET.get('per_page', 7)) 16 | t = get_home_template(page) 17 | 18 | return HttpResponse(t.get_html(request)) 19 | 20 | 21 | def category_article_list_view(request, category_id): 22 | """ 23 | """ 24 | page = int(request.GET.get('page', 1)) 25 | category = get_category_by_id(category_id) 26 | if not category: 27 | raise Http404() 28 | t = get_category_template(category, page) 29 | 30 | return HttpResponse(t.get_html(request)) 31 | 32 | 33 | def tag_article_list_view(request, tag_id): 34 | """ 35 | """ 36 | page = int(request.GET.get('page', 1)) 37 | tag = get_tag_by_id(tag_id) 38 | if not tag: 39 | raise Http404() 40 | t = get_tag_template(tag, page) 41 | 42 | return HttpResponse(t.get_html(request)) 43 | 44 | 45 | def detail_article_view(request, article_id): 46 | form_error = request.GET.get('form_error', '') 47 | article = get_article_by_id(article_id) 48 | if not article: 49 | raise Http404() 50 | meta_data = article.meta_data() 51 | meta_data.read_num += 1 52 | meta_data.save() 53 | t = get_detail_article_template(article, form_error) 54 | 55 | return HttpResponse(t.get_html(request)) 56 | 57 | 58 | def detail_flatpage_view(request, url): 59 | url_dict = get_flatpage_url_dict() 60 | try: 61 | page_id = url_dict[url] 62 | except: 63 | raise Http404() 64 | flatpage = get_flatpage_by_id(page_id) 65 | 66 | t = get_detail_flatpage_template(flatpage) 67 | 68 | return HttpResponse(t.get_html(request)) 69 | 70 | 71 | def page_not_found_view(request, exception): 72 | t = get_404_template() 73 | 74 | return HttpResponse(t.get_html(request)) 75 | -------------------------------------------------------------------------------- /base_theme/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/base_theme/__init__.py -------------------------------------------------------------------------------- /base_theme/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BaseThemeConfig(AppConfig): 5 | name = 'base_theme' 6 | 7 | deeru_type = 'theme' 8 | -------------------------------------------------------------------------------- /base_theme/manager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/base_theme/manager/__init__.py -------------------------------------------------------------------------------- /base_theme/static/base_theme/css/base_theme.css: -------------------------------------------------------------------------------- 1 | a { 2 | word-wrap: break-word; 3 | } 4 | 5 | .panel-child { 6 | display: none; 7 | 8 | } 9 | 10 | .panel-child > .panel-block { 11 | border-top-width: 0; 12 | padding-left: 50px; 13 | } 14 | 15 | .mpanel > .panel-heading { 16 | border: 0; 17 | /*border-bottom-color: red*/ 18 | } 19 | 20 | .mpanel > .panel-heading, .panel-block { 21 | background-color: white; 22 | border: 0; 23 | 24 | } 25 | 26 | .mpanel > .panel-block { 27 | padding-left: 20px; 28 | padding-right: 20px; 29 | } 30 | 31 | .mpanel { 32 | 33 | box-shadow: 0 2px 3px rgba(10, 10, 10, .1), 0 0 0 1px rgba(10, 10, 10, .1); 34 | border-radius: 6px; 35 | padding: 1px; 36 | 37 | } 38 | 39 | .aside_tag_a { 40 | margin-right: 10px; 41 | float: bottom; 42 | } 43 | 44 | .aside_tag_a:link { 45 | color: #363636 46 | } 47 | 48 | .aside_tag_a:visited { 49 | color: #363636 50 | } 51 | 52 | .aside_tag_a:hover { 53 | color: #ff4d3a 54 | } 55 | 56 | .aside_tag_a:active { 57 | color: #363636 58 | } 59 | 60 | .red_a:link { 61 | color: #363636 62 | } 63 | 64 | .red_a:visited { 65 | color: #363636 66 | } 67 | 68 | .red_a:hover { 69 | color: #ff4d3a 70 | } 71 | 72 | .red_a:active { 73 | color: #363636 74 | } 75 | 76 | .red_child_a:link { 77 | color: #777 78 | } 79 | 80 | .red_child_a:visited { 81 | color: #777 82 | } 83 | 84 | .red_child_a:hover { 85 | color: #ff4d3a 86 | } 87 | 88 | .red_child_a:active { 89 | color: #777 90 | } 91 | 92 | .aside_tag_a:last-child { 93 | margin-right: 0; 94 | } 95 | 96 | .border-bt-line { 97 | padding: 8px 8px 8px 8px; 98 | 99 | -webkit-box-sizing: border-box; 100 | -moz-box-sizing: border-box; 101 | box-sizing: border-box; 102 | border-bottom: 1px dashed #dbdbdb; 103 | width: 100%; 104 | text-overflow: ellipsis; 105 | white-space: nowrap; 106 | } 107 | 108 | .panel-scroll { 109 | padding-left: 50px; 110 | 111 | } 112 | 113 | .img_head { 114 | overflow: hidden; 115 | } 116 | 117 | .img_head p { 118 | display: inline-block; 119 | width: 100%; 120 | height: 100%; 121 | border-radius: 100px; 122 | border: 2px solid #fff; 123 | overflow: hidden; 124 | -webkit-box-shadow: 0 0 3px #ccc; 125 | box-shadow: 0 0 3px #ccc; 126 | } 127 | 128 | .img_head img { 129 | width: 100%; 130 | min-height: 100%; 131 | text-align: center; 132 | } 133 | 134 | .emoji_tabs { 135 | 136 | font-size: 21px; 137 | text-align: center; 138 | line-height: 25px; 139 | } 140 | 141 | .emoji_tabs > a { 142 | margin: 10px 5px 5px 5px; 143 | text-decoration: none; 144 | } 145 | 146 | .emoji_tabs + a { 147 | margin-top: 5px; 148 | } 149 | 150 | pre { 151 | background: #f0f0f0; 152 | tab-size: 4; 153 | overflow: auto; 154 | padding: 10px; 155 | border: 1px solid #e5e5e5; 156 | border-radius: 3px; 157 | } 158 | 159 | .article-header { 160 | border-bottom: 1px solid #e8e8e8; 161 | border-radius: 6px 6px 0 0; 162 | border-top: 1px solid #fff; 163 | background-color: #fdfdfd; 164 | position: relative; 165 | } 166 | 167 | .detail_article_box > * { 168 | padding: 20px; 169 | } 170 | 171 | .article_header_a:link { 172 | color: #696969 173 | } 174 | 175 | .article_header_a:visited { 176 | color: #696969 177 | } 178 | 179 | .article_header_a:hover { 180 | color: #ff4d3a 181 | } 182 | 183 | .article_header_a:active { 184 | color: #696969 185 | } 186 | 187 | .article_end { 188 | padding: 10px; 189 | 190 | font-size: 14px; 191 | border: 1px solid #ff4d3a; 192 | border-left-width: thick; 193 | } 194 | 195 | .footer a { 196 | color: #f5f5f5; 197 | border-bottom: dotted 1px #f5f5f5; 198 | 199 | } 200 | 201 | .footer a:hover { 202 | color: #f5f5f5; 203 | border-bottom: solid 1px #f5f5f5; 204 | 205 | } 206 | 207 | .footer strong { 208 | color: #f5f5f5; 209 | } 210 | 211 | .long_words { 212 | word-wrap: break-word; 213 | word-break: break-all; 214 | } -------------------------------------------------------------------------------- /base_theme/templates/base_theme/aside_right.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | 3 | 61 | 62 | -------------------------------------------------------------------------------- /base_theme/templates/base_theme/base.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | 3 | 4 | 5 | {% include "base_theme/head.html" %} 6 | {% block head_static %} 7 | {% endblock %} 8 | 9 | 10 | 11 | {% block body_navbar %} 12 | {% include "base_theme/body_navbar.html" %} 13 | {% endblock %} 14 | 15 | {% block body_section %} 16 |
    17 |
    18 | {% block columns %} 19 |
    20 | {% block columns_left %} 21 | {% endblock %} 22 | {% block columns_right %} 23 | {% endblock %} 24 |
    25 | {% endblock %} 26 |
    27 |
    28 | {% endblock %} 29 | {% block body_footer %} 30 | {% include 'base_theme/body_footer.html' %} 31 | {% endblock %} 32 | 33 | {% block after_body%} 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /base_theme/templates/base_theme/body_footer.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
    6 |
    7 |
    8 |

    {{ config.v2_blog_config.blog_name }}

    9 |

    10 | Powered by DeerU. The source code is licensed 11 | GPL V3 12 |

    13 |
    14 |
    15 |
    -------------------------------------------------------------------------------- /base_theme/templates/base_theme/body_navbar.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | 47 | 48 | 59 | 60 | -------------------------------------------------------------------------------- /base_theme/templates/base_theme/category.html: -------------------------------------------------------------------------------- 1 | {% extends "base_theme/home.html" %} 2 | 3 | {% block container_header %} 4 |
    5 | 12 |
    13 | {% endblock %} -------------------------------------------------------------------------------- /base_theme/templates/base_theme/detail_flatpage.html: -------------------------------------------------------------------------------- 1 | {% extends "base_theme/base.html" %} 2 | {% load base_theme_filter %} 3 | {% load static %} 4 | 5 | {% block head_static %} 6 | {{ flatpage.title }} |{{ config.v2_blog_config.title }} 7 | 8 | {% endblock %} 9 | {% block columns_left %} 10 |
    11 |
    12 |
    13 | 14 |
    15 |

    16 | {{ flatpage.content|safe }} 17 |

    18 |
    19 |
    20 |
    21 |
    22 |
    23 | 24 | {% endblock %} 25 | {% block columns_right %} 26 | {% include "base_theme/aside_right.html" %} 27 | {% endblock %} 28 | 29 | {% block after_body %} 30 | {% if config.v2_blog_config.baidu_auto_push %} 31 | 47 | 48 | {% endif %} 49 | {% endblock %} 50 | -------------------------------------------------------------------------------- /base_theme/templates/base_theme/head.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | {{ config.v2_blog_config.title }} 6 | 7 | 8 | {##} 9 | {##} 10 | 11 | -------------------------------------------------------------------------------- /base_theme/templates/base_theme/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base_theme/base.html" %} 2 | {% load base_theme_filter %} 3 | 4 | {% block columns_left %} 5 |
    6 | 7 |
    8 | {% block container_header %} {% endblock %} 9 | {% if article_list|length is 0 %} 10 |
    11 | 尚无文章 12 |
    13 | {% endif %} 14 | {% for a in article_list %} 15 |
    16 |

    17 | {{ a.title }} 18 | {% if a.sorted_num != 0 %} 19 | [置顶] 20 | {% endif %} 21 |

    22 |
    23 | 24 |

    {{ a.created_time|date }}

    25 | | 26 | 27 | 28 |

    {{ config.v2_blog_config.nickname }}

    29 | | 30 | 31 | 32 |

    评论:{{ a.meta_data.comment_num }}

    33 | | 34 | 35 | 36 |

    阅读:{{ a.meta_data.read_num }}

    37 | | 38 |
    39 | 40 |
    41 | {% if a.image %} 42 |
    43 | 45 |
    46 | {% endif %} 47 |
    48 |

    49 | {{ a.summary|safe }} 50 |

    51 |
    52 |
    53 |
    54 |
    55 | {% for t in a.tags %} 56 | {{ t.name }} 57 | {% endfor %} 58 |
    59 |
    60 | 阅读全文>> 61 |
    62 |
    63 |
    64 | {% endfor %} 65 | 66 | 78 |
    79 |
    80 | 81 | {% endblock %} 82 | 83 | {% block columns_right %} 84 | {% include "base_theme/aside_right.html" %} 85 | {% endblock %} -------------------------------------------------------------------------------- /base_theme/templates/base_theme/tag.html: -------------------------------------------------------------------------------- 1 | {% extends "base_theme/home.html" %} 2 | 3 | {% block container_header %} 4 |
    5 | 12 |
    13 | {% endblock %} -------------------------------------------------------------------------------- /base_theme/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/base_theme/templatetags/__init__.py -------------------------------------------------------------------------------- /base_theme/templatetags/base_theme_filter.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.utils.safestring import mark_safe 3 | 4 | from base_theme.manager.base_theme_manager import get_top_menu_htmltag_list, get_aside_category_htmltag, \ 5 | get_aside_tag_htmltag_list, get_comment_tree, get_page_html_list, get_img_tag 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.filter(name='top_menu') 11 | def top_menu(menu_config): 12 | return get_top_menu_htmltag_list(menu_config) 13 | 14 | 15 | @register.filter(name='get_img') 16 | def get_img(img_config): 17 | return mark_safe(get_img_tag(img_config)) 18 | 19 | 20 | @register.filter(name='type') 21 | def get_type(arg): 22 | return type(arg) 23 | 24 | 25 | @register.filter(name='aside_category') 26 | def aside_category(category): 27 | t = get_aside_category_htmltag(category) 28 | 29 | return t.format_html() 30 | 31 | 32 | @register.filter(name='aside_tags') 33 | def aside_tags(tags): 34 | if not tags: 35 | return [] 36 | t = get_aside_tag_htmltag_list(tags) 37 | return t 38 | 39 | # return shuffle(t) 40 | 41 | 42 | @register.filter(name='page_list') 43 | def page_list(paginator): 44 | return get_page_html_list(paginator) 45 | 46 | 47 | @register.filter(name='comment_tree') 48 | def comment_tree(comments): 49 | """ 50 | 返回排序后的评论树 51 | 以下说的 评论、回复 其实是一个东西,方便区分用了两个词,具体看类Comment的说明 52 | 53 | child:包含评论的回复,和对这条评论下回复的回复,child不会再有child 54 | 55 | [ { 'comment' : Comment , 'children': [ {'comment' : Comment, 'to_nickname':'xx'} ] } ,{...}] 56 | :return: 57 | """ 58 | t = get_comment_tree(comments) 59 | 60 | return t 61 | 62 | 63 | @register.filter(name='q_count') 64 | def q_count(q): 65 | """ 66 | queryset.count() 67 | :return: 68 | """ 69 | try: 70 | return q.count() 71 | except: 72 | return 0 73 | -------------------------------------------------------------------------------- /base_theme/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /base_theme2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/base_theme2/__init__.py -------------------------------------------------------------------------------- /base_theme2/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BaseTheme2Config(AppConfig): 5 | name = 'base_theme2' 6 | 7 | -------------------------------------------------------------------------------- /base_theme2/static/base_theme2/img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/base_theme2/static/base_theme2/img/avatar.jpg -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/article_list.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | 3 |
    4 |
    5 | {% include article_list_breadcrumb_template %} 6 | 7 | {% if article_list|length is 0 %} 8 | {% include article_list_empty_item_template %} 9 | {% endif %} 10 | {% for a in article_list %} 11 | {% include article_list_item_template %} 12 | {% endfor %} 13 | 14 | 26 |
    27 |
    -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/article_list_breadcrumb.html: -------------------------------------------------------------------------------- 1 | {% if breadcrumbs %} 2 |
    3 | 11 |
    12 | {% endif %} -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/article_list_empty_item.html: -------------------------------------------------------------------------------- 1 |
    2 | 尚无文章 3 |
    -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/article_list_item.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | 3 |
    4 |

    5 |

    6 | {% if a.sorted_num != 0 %}置顶{% endif %} 7 | {{ a.title }} 8 |

    9 |

    10 |
    11 | 12 |

    {{ a.created_time|date }}

    13 | | 14 | 15 | 16 |

    {{ config.v2_blog_config.nickname }}

    17 | | 18 | 19 | 20 |

    评论:{{ a.meta_data.comment_num }}

    21 | | 22 | 23 | 24 |

    阅读:{{ a.meta_data.read_num }}

    25 | | 26 |
    27 | 28 |
    29 | {% if a.image %} 30 |
    31 | 33 |
    34 | {% endif %} 35 |
    36 |

    37 | {{ a.summary|safe }} 38 |

    39 |
    40 |
    41 |
    42 |
    43 | {% for t in a.tags %} 44 | {{ t.name }} 45 | {% endfor %} 46 |
    47 |
    48 | 阅读全文>> 49 |
    50 |
    51 |
    52 | -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/base2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head_begin_template %} 5 | 6 | 7 | {{ head_title }} 8 | {% include head_static_template %} 9 | {% include head_end_template %} 10 | 11 | 12 | 13 | {% include body_begin_template %} 14 | 15 | {% include body_navbar_template %} 16 | 17 | {% include body_section_template %} 18 | 19 | {% include body_footer_template %} 20 | 21 | {% include body_end_template %} 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/body_footer.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | {% include body_footer_begin_template %} 5 |

    {{ config.v2_blog_config.blog_name }}

    6 |

    7 | Powered by DeerU. The source code is 8 | licensed 9 | GPL V3 10 |

    11 | {% include body_footer_end_template %} 12 | 13 |
    14 |
    15 |
    -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/body_navbar.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | 24 | 25 | 34 | 35 | -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/body_navbar_left.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | 3 | {% if config.v2_iconbar_config.left.logo %} 4 | 7 | {% endif %} 8 | 9 | {% if config.v2_iconbar_config.left.blog_name %} 10 | 11 | {% if config.v2_iconbar_config.left.blog_name|type == 'str' %} 12 | {{ config.v2_iconbar_config.left.blog_name }} 13 | {% else %} 14 | 15 | 16 | {{ config.v2_iconbar_config.left.blog_name.text }} 17 | {% endif %} 18 | 19 | {% endif %} -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/body_navbar_menu.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | {% for m in config.v2_navbar_config.menu|top_menu %} 3 | {{ m.format_html }} 4 | {% endfor %} -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/body_navbar_right.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | {% for item in config.v2_iconbar_config.right %} 3 | 4 | {{ item.img|get_img }} 5 | {{ item.name }} 6 | 7 | {% endfor %} -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/body_section.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | {% include body_section_begin_template %} 5 | {% include body_section_content_template %} 6 | {% include body_section_sidebar_template %} 7 | {% include body_section_end_template %} 8 | 9 |
    10 |
    11 |
    -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/body_section_sidebar.html: -------------------------------------------------------------------------------- 1 | {% load base_theme_filter %} 2 | 3 | 58 | 59 | -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/detail_article.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | {% include detail_article_content_template %} 4 | {% include detail_article_comment_template %} 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/detail_article_head_begin.html: -------------------------------------------------------------------------------- 1 | {% load base_theme2_filter %} 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/detail_flatpage.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |

    7 | {{ flatpage.content|safe }} 8 |

    9 |
    10 |
    11 |
    12 |
    13 |
    14 | 15 | -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/empty.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/base_theme2/templates/base_theme2/empty.html -------------------------------------------------------------------------------- /base_theme2/templates/base_theme2/head_static.html: -------------------------------------------------------------------------------- 1 | {% for c in css %} 2 | {% endfor %} 3 | {% for j in js %} 4 | {% endfor %} -------------------------------------------------------------------------------- /base_theme2/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/base_theme2/templatetags/__init__.py -------------------------------------------------------------------------------- /base_theme2/templatetags/base_theme2_filter.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from tool.html_helper import clean_all_tags 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter(name='clear_tag') 9 | def clear_tag(s): 10 | """ 11 | queryset.count() 12 | :return: 13 | """ 14 | 15 | s= clean_all_tags(s).replace(' ', ' ').replace('\n', ' ') 16 | return s 17 | -------------------------------------------------------------------------------- /base_theme2/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /building.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/building.png -------------------------------------------------------------------------------- /data/Alibaba-PuHuiTi-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/data/Alibaba-PuHuiTi-Regular.ttf -------------------------------------------------------------------------------- /deeru/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.0.0' 2 | __release__ = '2.0.0 - beta' 3 | -------------------------------------------------------------------------------- /deeru/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | 优先在settings_local中修改添加配置 3 | """ 4 | from app.deeru_config_handler.base import BaseHandler 5 | from deeru.settings_common import * 6 | from deeru.settings_local import * 7 | from app.deeru_expression.expressions import BaseExpression 8 | 9 | # app 10 | INSTALLED_APPS += CUSTOM_APPS 11 | 12 | # 导入表达式 13 | from importlib import import_module 14 | 15 | EXPRESSION_DICT = {} 16 | for f in EXPRESSION + CUSTOM_EXPRESSION: 17 | exp_mod = import_module(f) 18 | for name in dir(exp_mod): 19 | if name.startswith('_') or name == 'BaseExpression': 20 | continue 21 | try: 22 | _class = getattr(exp_mod, name) 23 | if isinstance(_class(None), BaseExpression): 24 | EXPRESSION_DICT[name.lower()] = _class 25 | except: 26 | pass 27 | 28 | # v2的config handler 29 | CONFIG_HANDLER_DICT = {} 30 | for f in CONFIG_HANDLER + CUSTOM_CONFIG_HANDLER: 31 | handler_mod = import_module(f) 32 | for name in dir(handler_mod): 33 | try: 34 | _class = getattr(handler_mod, name) 35 | name = getattr(_class, 'deeru_config_handler_name', None) 36 | if name: 37 | CONFIG_HANDLER_DICT[name] = _class 38 | except: 39 | pass 40 | 41 | # log 42 | if LOG_DIR: 43 | LOGGING = { 44 | 'version': 1, 45 | 'disable_existing_loggers': False, 46 | 47 | 'formatters': { 48 | 'verbose': { 49 | 'format': '{asctime} {levelname} {module}.{funcName} Line:{lineno} {message}', 50 | 'formatTime': '%Y-%m-%d %H:%M:%S', 51 | 'style': '{', 52 | }, 53 | 'simple': { 54 | 'format': '{asctime} {levelname} {message}', 55 | 'formatTime': '%Y-%m-%d %H:%M:%S', 56 | 'style': '{', 57 | 58 | }, 59 | 'simple2': { 60 | 'format': '{asctime} {message}', 61 | 'style': '{', 62 | } 63 | }, 64 | 65 | 'handlers': { 66 | 'console': { 67 | 'level': 'DEBUG', 68 | 'class': 'logging.StreamHandler', 69 | 'formatter': 'simple' 70 | }, 71 | 72 | 'default_err': { 73 | 'level': 'ERROR', 74 | 'class': 'logging.handlers.WatchedFileHandler', 75 | 'filename': os.path.join(LOG_DIR, 'error.log'), 76 | 'formatter': 'verbose', 77 | }, 78 | 79 | 'info': { 80 | 'level': 'INFO', 81 | 'class': 'logging.handlers.WatchedFileHandler', 82 | 'filename': os.path.join(LOG_DIR, 'info.log'), 83 | 'formatter': 'verbose', 84 | }, 85 | 86 | }, 87 | 88 | 'loggers': { 89 | 'django': { 90 | 'handlers': ['console'], 91 | 'propagate': False, 92 | 'level': 'INFO', 93 | }, 94 | 'django.request': { 95 | 'handlers': ['default_err', 'console'], 96 | 'level': 'ERROR', 97 | 'propagate': False, 98 | }, 99 | 'error_logger': { 100 | 'handlers': ['default_err', 'console'], 101 | 'level': 'ERROR', 102 | 'propagate': False, 103 | }, 104 | 'info_logger': { 105 | 'handlers': ['info', 'console'] if DEBUG else ['info'], 106 | 'level': 'INFO', 107 | 'propagate': False, 108 | }, 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /deeru/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.urls import path, include 5 | 6 | urlpatterns = [ 7 | path('jet/', include('jet.urls', 'jet')), # Django JET URLS 8 | path('jet/dashboard/', include('jet.dashboard.urls', 'jet-dashboard')), 9 | path('froala_editor/', include('froala_editor.urls')), 10 | path('captcha/', include('captcha.urls')), 11 | 12 | path('', include('deeru.urls_local')), 13 | ] 14 | 15 | if settings.DEBUG: 16 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 17 | 18 | handler404 = 'app.views.views_v2.page_not_found_view' 19 | -------------------------------------------------------------------------------- /deeru/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for deeru project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "deeru.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /deeru_cmd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/deeru_cmd/__init__.py -------------------------------------------------------------------------------- /deeru_cmd/app_templates/MANIFEST.in-tpl: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include MANIFEST.in 3 | include README.rst 4 | graft {{ app_name }} 5 | global-exclude __pycache__ 6 | global-exclude *.py[co] 7 | recursive-include {{ app_name }}/static * 8 | recursive-include {{ app_name }}/templates * -------------------------------------------------------------------------------- /deeru_cmd/app_templates/README.rst-tpl: -------------------------------------------------------------------------------- 1 | {{ app_camel_name }} 2 | ===================== 3 | 4 | {{ app_name }} is a {{ deeru_type }} for DeerU -------------------------------------------------------------------------------- /deeru_cmd/app_templates/apps.py-tpl: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class {{ app_camel_name }}Config(AppConfig): 5 | name = '{{app_name}}' 6 | 7 | # 获取插件、主题的专业配置路径 8 | deeru_config_context='{{app_name}}.consts.{{app_name}}_config_context' 9 | 10 | # 下面几项暂时没用 11 | 12 | # 类型 13 | deeru_type='{{deeru_type}}' 14 | 15 | # 别名,插件、主题列表中显示的名字 16 | nice_name='' 17 | 18 | url='' 19 | 20 | author='' 21 | 22 | description='' 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /deeru_cmd/app_templates/consts.py-tpl: -------------------------------------------------------------------------------- 1 | 2 | # 专有配置,这里面的配置会添加到view的context中 3 | # name:nice_name 4 | # name - context里的名字 5 | # nice_name - 数据库里保存的名字 6 | # 如:'top_ico' : '顶部图标栏' 7 | {{app_name}}_config_context={ 8 | 9 | } -------------------------------------------------------------------------------- /deeru_cmd/app_templates/empty.py-tpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/deeru_cmd/app_templates/empty.py-tpl -------------------------------------------------------------------------------- /deeru_cmd/app_templates/git_add.py-tpl: -------------------------------------------------------------------------------- 1 | git add .gitignore 2 | git add {{ app_name }}* 3 | git add README.* 4 | git add MANIFEST.in 5 | git add {{ app_name }}_setup.py -------------------------------------------------------------------------------- /deeru_cmd/app_templates/setup.py-tpl: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | with open("README.rst", encoding='utf-8') as f: 6 | long_description = f.read() 7 | 8 | setup( 9 | name="{{app_name}}", 10 | version="", 11 | description="", 12 | long_description=long_description, 13 | license="", 14 | 15 | url="", 16 | author="", 17 | author_email="", 18 | python_requires='>=3.5', 19 | 20 | packages=find_packages(include=['{{app_name}}*', ]), 21 | 22 | 23 | classifiers=[ 24 | 'Environment :: Web Environment', 25 | 'Framework :: Django', 26 | 'Intended Audience :: Developers', 27 | 'Programming Language :: Python :: 3', 28 | 'Programming Language :: Python :: 3.5', 29 | 'Programming Language :: Python :: 3.6', 30 | 'Development Status :: 3 - Alpha', 31 | 'Topic :: Internet :: WWW/HTTP', 32 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 33 | 'Topic :: Internet :: WWW/HTTP :: WSGI', 34 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 35 | 'Topic :: Software Development :: Libraries :: Python Modules', 36 | 37 | 38 | ], 39 | 40 | 41 | ) 42 | -------------------------------------------------------------------------------- /deeru_cmd/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DeerUCmdConfig(AppConfig): 5 | name = 'deeru_cmd' 6 | -------------------------------------------------------------------------------- /deeru_cmd/bin/deeru_admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | import os 5 | import sys 6 | 7 | import pkg_resources 8 | from django.core import management 9 | from django.conf import settings 10 | 11 | help_text = [ 12 | 'Deeru内置命令表:', 13 | '', '', 14 | 'install', 15 | 'new', 16 | 'version', 17 | 'upgrade' 18 | ] 19 | 20 | 21 | def run(): 22 | settings_path = os.path.join(os.getcwd(), 'deeru') 23 | settings_py = os.path.join(settings_path, 'settings.py') 24 | 25 | if os.path.exists(settings_py): 26 | sys.path.insert(0, os.getcwd()) 27 | os.environ['DJANGO_SETTINGS_MODULE'] = 'deeru.settings' 28 | else: 29 | settings.configure(INSTALLED_APPS=['deeru_cmd.apps.DeerUCmdConfig']) 30 | 31 | management.execute_from_command_line() 32 | 33 | 34 | if __name__ == "__main__": 35 | run() 36 | -------------------------------------------------------------------------------- /deeru_cmd/management/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import sys 4 | from pathlib import Path 5 | 6 | from django.core.management import BaseCommand, CommandError 7 | from django.core.management.base import OutputWrapper 8 | 9 | 10 | class DeerUBaseCommand(BaseCommand): 11 | NEED_PROJECT = False 12 | templates_dir = '' 13 | 14 | def __init__(self, stdout=None, stderr=None, no_color=False): 15 | super().__init__(stdout, stderr, no_color) 16 | 17 | # 检测目录位置 18 | if self.NEED_PROJECT: 19 | settings_path = os.path.join(os.getcwd(), 'deeru') 20 | settings_py = os.path.join(settings_path, 'settings.py') 21 | 22 | if not os.path.exists(settings_py): 23 | raise CommandError('该命令需要在工程目录下运行') 24 | 25 | self.error = self.stderr.write 26 | 27 | info_out = OutputWrapper(sys.stdout) 28 | info_out.style_func = self.style.WARNING 29 | self.info = info_out.write 30 | 31 | success_out = OutputWrapper(sys.stdout) 32 | success_out.style_func = self.style.SUCCESS 33 | self.success = success_out.write 34 | 35 | def get_template_str(self, template_name): 36 | import deeru_cmd 37 | template_dir = deeru_cmd.__path__[0] 38 | templdate_file = Path(template_dir) / Path(self.templates_dir) / Path(template_name) 39 | return templdate_file.read_text() 40 | -------------------------------------------------------------------------------- /deeru_cmd/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/deeru_cmd/management/commands/__init__.py -------------------------------------------------------------------------------- /deeru_cmd/management/commands/install.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import platform 4 | import shutil 5 | import subprocess 6 | from pathlib import Path 7 | 8 | from django.core.management import CommandError 9 | from django.core.management.utils import get_random_secret_key 10 | from django.template import Context, Engine 11 | 12 | from deeru_cmd.management.base import DeerUBaseCommand 13 | 14 | 15 | class Command(DeerUBaseCommand): 16 | """ 17 | 下载deeru项目 18 | python manage.py install_project 19 | """ 20 | 21 | DEERU_GIT_URL = 'https://github.com/gojuukaze/DeerU.git' 22 | templates_dir = 'project_templates' 23 | 24 | def add_arguments(self, parser): 25 | parser.description = '''安装下载DeerU项目、插件、主题''' 26 | 27 | parser.add_argument('name', type=str, help='名称') 28 | 29 | parser.add_argument( 30 | '--branch', 31 | default='master', 32 | dest='branch', 33 | help='从哪个分支下载', 34 | ) 35 | 36 | def get_project_templates(self): 37 | return [ 38 | ['settings_local.py-tpl', Path(self.name) / Path('deeru/settings_local.py')], 39 | ['urls_local.py-tpl', Path(self.name) / Path('deeru/urls_local.py')] 40 | ] 41 | 42 | def install_project(self): 43 | self.info('开始安装DeerU') 44 | 45 | self.info('下载DeerU ...') 46 | 47 | s = '' 48 | if os.path.exists(self.name): 49 | # self.info('已存在相同目录 "%s" ,请选择: d(删除已存在目录); s(跳过下载) ' % self.name) 50 | s = input('已存在相同目录 "%s" ,请选择: d(删除已存在目录); s(跳过下载): ' % self.name) 51 | if s == 'd': 52 | shutil.rmtree(self.name) 53 | elif s == 's': 54 | pass 55 | else: 56 | raise CommandError('输入错误') 57 | 58 | if s != 's': 59 | result = subprocess.run('git clone -b %s %s %s' % (self.branch, self.DEERU_GIT_URL, self.name), shell=True) 60 | if result.returncode != 0: 61 | raise CommandError('\n下载DeerU失败') 62 | 63 | self.info('安装依赖...') 64 | result = subprocess.run('pip install -r requirements.txt', cwd=self.name, shell=True) 65 | if result.returncode != 0: 66 | raise CommandError('\n安装依赖失败') 67 | 68 | self.info('复制必要文件...') 69 | 70 | context = Context({'SECRET_KEY': get_random_secret_key()}, autoescape=False) 71 | 72 | for template_name, new_file in self.get_project_templates(): 73 | template = Engine().from_string(self.get_template_str(template_name)) 74 | content = template.render(context) 75 | new_file.write_text(content) 76 | 77 | self.success('\n安装完成 !!') 78 | 79 | def handle(self, *args, **options): 80 | self.name = options['name'] 81 | self.branch = options['branch'] 82 | 83 | self.install_project() 84 | -------------------------------------------------------------------------------- /deeru_cmd/management/commands/starttheme.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import platform 4 | import shutil 5 | import subprocess 6 | from pathlib import Path 7 | from django.core.management import CommandError 8 | from django.core import management 9 | from django.template import Engine, Context 10 | 11 | from deeru_cmd.management.base import DeerUBaseCommand 12 | 13 | 14 | class Command(DeerUBaseCommand): 15 | """ 16 | python manage.py starttheme xx 17 | """ 18 | NEED_PROJECT = True 19 | templates_dir = 'app_templates' 20 | 21 | def add_arguments(self, parser): 22 | parser.description = '''创建DeerU主题目录''' 23 | 24 | parser.add_argument('name', type=str, help='名称') 25 | 26 | def mk_dir(self, dir_name): 27 | for name in dir_name: 28 | os.mkdir(Path(self.name) / name) 29 | 30 | def rm_file(self, files): 31 | for f in files: 32 | f = Path(self.name) / f 33 | if f.is_file(): 34 | os.remove(f) 35 | else: 36 | shutil.rmtree(f) 37 | 38 | def handle(self, *args, **options): 39 | self.name = options['name'] 40 | management.call_command('startapp', self.name) 41 | 42 | self.mk_dir(['static', Path('static/', self.name), 'templates', Path('templates/', self.name)]) 43 | 44 | self.rm_file(['migrations', 'models.py', 'tests.py', 'views.py']) 45 | -------------------------------------------------------------------------------- /deeru_cmd/management/commands/upgrade.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import platform 4 | import shutil 5 | import subprocess 6 | from django.core.management import CommandError 7 | 8 | from deeru_cmd.management.base import DeerUBaseCommand 9 | 10 | 11 | class Command(DeerUBaseCommand): 12 | """ 13 | 升级 14 | python manage.py upgrade 15 | """ 16 | NEED_PROJECT = True 17 | 18 | def add_arguments(self, parser): 19 | parser.description = '''升级DeerU''' 20 | 21 | parser.add_argument( 22 | '--branch', 23 | default='master', 24 | dest='branch', 25 | help='从哪个分支升级', 26 | ) 27 | 28 | def handle(self, *args, **options): 29 | branch = options['branch'] 30 | subprocess.run('git reset --hard', shell=True) 31 | result = subprocess.run('git pull origin '+branch, shell=True) 32 | if result.returncode != 0: 33 | raise CommandError('\n拉取最新版本失败,请参照手动升级教程升级') 34 | result =subprocess.run('pip install -r requirements.txt', shell=True) 35 | if result.returncode != 0: 36 | raise CommandError('\n安装依赖失败,请参照手动升级教程升级') 37 | self.success('\n升级完成,运行 python manage.py init_deeru 更新必要配置') 38 | 39 | -------------------------------------------------------------------------------- /deeru_cmd/project_templates/settings_local.py-tpl: -------------------------------------------------------------------------------- 1 | SECRET_KEY = '{{ SECRET_KEY }}' 2 | 3 | DEBUG = True 4 | 5 | ALLOWED_HOSTS = ['*'] 6 | 7 | # v1版配置表达式,已弃用 8 | CUSTOM_EXPRESSION = [] 9 | 10 | # v2版配置handler 11 | CUSTOM_CONFIG_HANDLER = [] 12 | 13 | CUSTOM_APPS = [ 14 | 15 | ] 16 | 17 | # log目录,需要启用log则去掉注释配置目录 18 | # LOG_DIR = '' 19 | -------------------------------------------------------------------------------- /deeru_cmd/project_templates/urls_local.py-tpl: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path('admin/', admin.site.urls), 6 | path('', include('app.urls')), 7 | # path('admin/doc/', include('django.contrib.admindocs.urls')), 8 | ] 9 | -------------------------------------------------------------------------------- /deeru_cmd/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /deeru_dashboard/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/deeru_dashboard/__init__.py -------------------------------------------------------------------------------- /deeru_dashboard/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DeeruDashboardConfig(AppConfig): 5 | name = 'deeru_dashboard' 6 | -------------------------------------------------------------------------------- /deeru_dashboard/dashboard.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from jet.dashboard import modules 3 | from jet.dashboard.dashboard import Dashboard 4 | 5 | from deeru_dashboard.dashboard_modules import CountModule, CommentModule 6 | 7 | 8 | class CustomIndexDashboard(Dashboard): 9 | 10 | columns = 2 11 | 12 | def init_with_context(self, context): 13 | self.children.append(CountModule('统计', column=0, order=0)) 14 | 15 | self.children.append(modules.LinkList( 16 | '快捷操作', 17 | children=[ 18 | { 19 | 'title': '博客首页', 20 | 'url': '/', 21 | 'external': True, 22 | }, 23 | { 24 | 'title': '创建文章', 25 | 'url': reverse('admin:app_article_add'), 26 | 'external': True, 27 | }, 28 | { 29 | 'title': 'DeerU - 开源博客框架', 30 | 'url': 'https://github.com/gojuukaze/DeerU', 31 | 'external': True, 32 | }, 33 | ], 34 | column=0, 35 | order=1 36 | )) 37 | 38 | self.children.append(CommentModule('待审核评论', column=0, order=2)) 39 | 40 | self.children.append(modules.RecentActions('操作记录', 10, column=1, order=1)) 41 | -------------------------------------------------------------------------------- /deeru_dashboard/dashboard_modules.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Sum, Count 2 | from jet.dashboard.modules import DashboardModule 3 | 4 | from app.consts import CommentStatusChoices 5 | from app.db_manager.content_manager import all_article, all_comment, all_flatpage, filter_created_comment 6 | 7 | 8 | class CountModule(DashboardModule): 9 | template = 'deeru_dashboard/count.html' 10 | 11 | def __init__(self, title=None, **kwargs): 12 | super().__init__(title, **kwargs) 13 | 14 | def init_with_context(self, context): 15 | article_count = all_article().count() 16 | comment_count = all_comment().count() 17 | flatpage_count = all_flatpage().count() 18 | 19 | self.children = [[article_count, '文章数'], 20 | [comment_count, '评论数'], 21 | [flatpage_count, '单页面数']] 22 | 23 | 24 | class CommentModule(DashboardModule): 25 | template = 'deeru_dashboard/comment.html' 26 | 27 | def __init__(self, title=None, **kwargs): 28 | super().__init__(title, **kwargs) 29 | 30 | def init_with_context(self, context): 31 | comments = filter_created_comment().order_by('-id')[:10] 32 | 33 | self.children = list(comments) 34 | -------------------------------------------------------------------------------- /deeru_dashboard/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /deeru_dashboard/templates/deeru_dashboard/comment.html: -------------------------------------------------------------------------------- 1 | {% load dashboard_tags %} 2 | 3 | 4 | 7 |
      8 | {% for comment in module.children %} 9 |
    • 10 |
      11 |

      {{ comment.nickname }} 发表在《 {{ comment.article.title }}

      13 |
      {{ comment.content | clean:"40"|safe }}
      14 | 23 |
      24 | {% csrf_token %} 25 | 26 | 27 |
      28 |
      29 |
    • 30 | 38 | {% empty %} 39 |
    • 暂无数据
    • 40 | {% endfor %} 41 |
    • 所有待审评论
    • 42 | 43 |
    44 | -------------------------------------------------------------------------------- /deeru_dashboard/templates/deeru_dashboard/count.html: -------------------------------------------------------------------------------- 1 |
    2 | {% for c in module.children %} 3 |
    4 |

    {{ c.0 }}

    5 |

    {{ c.1 }}

    6 | 7 |
    8 | {% endfor %} 9 | 10 |
    -------------------------------------------------------------------------------- /deeru_dashboard/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/deeru_dashboard/templatetags/__init__.py -------------------------------------------------------------------------------- /deeru_dashboard/templatetags/dashboard_tags.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django import template 4 | from django.utils.safestring import mark_safe 5 | 6 | from tool.html_helper import clean_all_tags 7 | 8 | register = template.Library() 9 | 10 | 11 | @register.filter(name='clean') 12 | def clean(content, length=None): 13 | """ 14 | content截短 15 | :param length: 16 | :type length: 17 | :param content: 18 | :type content: str 19 | :return: 20 | :rtype: 21 | """ 22 | content = content.replace(' ', ' ') 23 | content = re.sub('<.*?>', '', content) 24 | if length: 25 | length = int(length) 26 | return clean_all_tags(content)[:length] + '...' 27 | else: 28 | return clean_all_tags(content) 29 | -------------------------------------------------------------------------------- /deeru_dashboard/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /deeru_dashboard/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /deeru_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/deeru_green.png -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/admin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/admin.jpg -------------------------------------------------------------------------------- /docs/_static/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/admin.png -------------------------------------------------------------------------------- /docs/_static/admin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/admin2.png -------------------------------------------------------------------------------- /docs/_static/admin3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/admin3.png -------------------------------------------------------------------------------- /docs/_static/config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/config.jpg -------------------------------------------------------------------------------- /docs/_static/deeru_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/deeru_green.png -------------------------------------------------------------------------------- /docs/_static/detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/detail.png -------------------------------------------------------------------------------- /docs/_static/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/home.png -------------------------------------------------------------------------------- /docs/_static/logo_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/logo_black.png -------------------------------------------------------------------------------- /docs/_static/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/p1.png -------------------------------------------------------------------------------- /docs/_static/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/p2.png -------------------------------------------------------------------------------- /docs/_static/p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/p3.png -------------------------------------------------------------------------------- /docs/_static/templates.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/templates.jpg -------------------------------------------------------------------------------- /docs/_static/ui_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gojuukaze/DeerU/2b103a7bdbc74cb27cad8b3e92deb95435a0b73b/docs/_static/ui_config.png -------------------------------------------------------------------------------- /docs/change_log.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Change Log 3 | ============ 4 | 5 | 2.0.1 6 | --------- 7 | 8 | * 替换主题中的cdn连接 9 | * 修改初始文章 10 | 11 | 2.0.0 12 | --------- 13 | 14 | * 修复下一篇按钮的连接bug 15 | * 升级js以及py包 16 | * view_class的context规范化 17 | * 修改文章简介、图片的提取逻辑 18 | * 配置可视化 19 | * 评论支持审核 20 | * 修改评论数统计规则,只统计对文章的评论 21 | * admin增加仪表盘 22 | * 新回复邮件提醒功能 23 | * 增加日志功能 24 | * 评论增加验证码 25 | 26 | 1.1.0 -alpha 27 | ---------------- 28 | 29 | * 评论root_id,to_id规范 30 | * 评论参数验证 31 | * fix bug `#6 `_ 32 | * 升级django版本,解决低版本安全问题 `CVE-2018-14574 Detail `_ 33 | 34 | 1.0.0 35 | ---------------- 36 | 37 | * 第一个正式版发布 38 | 39 | 0.2.0 - alpha 40 | ----------------- 41 | 42 | * 单页面 43 | * 修改安装方式 44 | * 重新设计表达式 45 | * 添加了一些内置命令 46 | * 完善文档 47 | * 修改bug 48 | 49 | 0.1.0 - alpha 50 | -------------- 51 | 52 | * 第一个测试版完成 53 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | import sphinx_rtd_theme 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'DeerU' 21 | copyright = '2021, gojuukaze' 22 | author = 'gojuukaze' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '2.1.0' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx_rtd_theme' 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The language for content autogenerated by Sphinx. Refer to documentation 41 | # for a list of supported languages. 42 | # 43 | # This is also used if you do content translation via gettext catalogs. 44 | # Usually you set "language" from the command line for these cases. 45 | language = 'zh_CN' 46 | 47 | # List of patterns, relative to source directory, that match files and 48 | # directories to ignore when looking for source files. 49 | # This pattern also affects html_static_path and html_extra_path. 50 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 51 | 52 | 53 | # -- Options for HTML output ------------------------------------------------- 54 | 55 | # The theme to use for HTML and HTML Help pages. See the documentation for 56 | # a list of builtin themes. 57 | # 58 | html_theme = 'sphinx_rtd_theme' 59 | 60 | # Add any paths that contain custom static files (such as style sheets) here, 61 | # relative to this directory. They are copied after the builtin static files, 62 | # so a file named "default.css" will overwrite the builtin "default.css". 63 | html_static_path = ['_static'] -------------------------------------------------------------------------------- /docs/developer_guide/config/handler.rst: -------------------------------------------------------------------------------- 1 | .. _handler: 2 | 3 | ================= 4 | 配置handler 5 | ================= 6 | 7 | 有时候还需要对保存的配置进行进一步处理,以获得最终需要的配置,因此引入了handler的概念。 8 | 9 | 保存配置时会把 ``v2_config`` 中的类型为dict且含有 ``_handler`` 这个key的那部分,经过handler处理后整个替换掉。 10 | 如果存在多层嵌套,会先处理最里层的数据。 11 | 12 | 如: 13 | 14 | .. code-block:: python 15 | 16 | { 17 | "url":{ 18 | "_handler":"v2_url_handler", 19 | "type":"tag", 20 | "value":"1" 21 | } 22 | } 23 | 24 | 经v2_url_handler处理后,变成: 25 | 26 | .. code-block:: json 27 | 28 | // 注意这里替换的是与"_handler"同级的整个结构 29 | { 30 | "url" : "/tag/1" 31 | } 32 | 33 | 34 | 自带handler 35 | ------------------ 36 | 37 | 自带了3个handler,分别是 : 38 | 39 | * v2_url_handler :返回分类、标签的url 40 | * v2_img_handler : 返回生成图片标签所以的结构 41 | * v2_kv_handler : 把数组的k-v结构解析为字典结构 42 | 43 | 自定义handler 44 | ----------------- 45 | 46 | 1. 新建一个python包,以及py文件, 47 | 48 | .. code-block:: python 49 | 50 | my_ex/ 51 | __init__.py 52 | custom_handler.py 53 | 54 | 2. 把你的py文件加入 ``settings_local.py`` 的 ``CUSTOM_CONFIG_HANDLER`` 中 55 | 56 | .. code-block:: python 57 | 58 | CUSTOM_CONFIG_HANDLER=['my_ex.custom_handler'] 59 | 60 | 3. 编写一个你的handler类,继承 ``app.deeru_config_handler.base.BaseHandler``,并重写 ``calculate()`` 61 | 62 | 装饰器 ``deeru_config_handler()`` 用于定义handler的名字,防止冲突建议格式为:``你的名字:handler名字`` 63 | 64 | 65 | .. code-block:: python 66 | 67 | from app.deeru_config_handler.base import BaseHandler, deeru_config_handler 68 | 69 | @deeru_config_handler('gojuukaze:textHandler') 70 | class TextHandler(BaseHandler): 71 | """ 72 | args: 73 | { 74 | '_handler':'gojuukaze:textHandler', 75 | 'text':'xxx' 76 | } 77 | 78 | 返回 79 | { 80 | 'text':'xx', 81 | } 82 | """ 83 | def calculate(self): 84 | text = self.args['text'] 85 | 86 | return { 87 | 'text': text, 88 | } 89 | 90 | 91 | 至此你已经成功编写了一个handler,载入handler需要重启工程 92 | 93 | .. note:: 94 | 95 | 函数 ``calculate()`` 并没有限制返回的数据类型,你可以返回字符串、字典等 96 | 97 | 要测试你写的handler,可以用 ``get_real_config()`` 函数 98 | 99 | .. code-block:: 100 | 101 | In [1]: from app.manager.config_manager_v2 import get_real_config 102 | In [2]: get_real_config({'_handler':'gojuukaze:textHandler','text':'123'}) 103 | 104 | Out[2]: {'text': '123'} -------------------------------------------------------------------------------- /docs/developer_guide/contribution.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | 贡献代码 3 | ==================== 4 | 5 | 你发现了bug或者优化了代码并且想合并到主分支? 6 | 7 | 首先你需要fork代码到你的仓库,然后切换到dev分支,在dev分支上开发完成后再github中提交 pull request,合并到dev分支。 8 | 9 | .. note:: 10 | 11 | 只接受合并到dev分支的pull request -------------------------------------------------------------------------------- /docs/developer_guide/expression/custom_expression.rst: -------------------------------------------------------------------------------- 1 | .. _custom-expression: 2 | 3 | ================= 4 | 自定义代码表达式 5 | ================= 6 | 7 | DeerU只提供了几个简单的代码表达式,你可以根据需要自定义你的表达式, 8 | 9 | 另外:表达式的名字最终会转为小写,因此IMG和Img是重名的,为了防止重名,自定义的表达式建议以自己的名字开头 10 | 11 | .. note:: 12 | 13 | 为了方便,以下所说的表达式特指代码表达式 14 | 15 | ----------------- 16 | 17 | 编写自定义表达式 18 | ----------------- 19 | 20 | 21 | 下面我们开始创建自定义表达式: 22 | 23 | 1. 新建一个python包,以及py文件, 24 | 25 | .. code-block:: python 26 | 27 | my_ex/ 28 | __init__.py 29 | custom_expression.py 30 | 31 | 2. 把你的py文件加入 ``settings_local.py`` 的 ``CUSTOM_EXPRESSION`` 中 32 | 33 | .. code-block:: python 34 | 35 | CUSTOM_EXPRESSION=['my_ex.custom_expression'] 36 | 37 | 3. 编写一个你的表达式类,继承 ``app.deeru_expression.expressions.BaseExpression``,并重写 ``calculate()`` 38 | 39 | 函数 ``format_expression()`` 解析表达式时会把表达式分为 表达式名、参数 两部分, 40 | 41 | 这里再次强调以下,表达式名(也就是类名)最终会转为小写 42 | 43 | 参数 会放到类的成员变量 args 里 44 | 45 | 表达式名、参数 一定是用'|'分割开,如: ``{% text | some args %}`` 46 | 47 | 参数部分没有限制,你可以仍然用'|'分割,也可自定义你的参数格式 48 | 49 | ``calculate()`` 的作用是解析参数,并返回需要的结果,它会在执行 ``get_result()`` 时调用。注意: ``calculate()`` 只会在第一次调用 ``get_result()`` 时执行, 50 | 后面将返回缓存的结果,因此同一个表达式实例不能重复使用 51 | 52 | 53 | .. code-block:: python 54 | 55 | from app.deeru_expression.expressions import BaseExpression,get_attrs 56 | 57 | 58 | class MText(BaseExpression): 59 | """ 60 | 字符表达式 61 | {% text| 值 [ | 其他属性] %} 62 | 63 | 返回{ 64 | 'text':'xx', 65 | 'attrs':{ 66 | 'style':'xx' 67 | } 68 | } 69 | """ 70 | 71 | def calculate(self): 72 | if not self.args: 73 | self.args = '' 74 | 75 | # 这里默认用'|'分割 76 | args = self.args.split('|') 77 | 78 | if len(args) == 0: 79 | raise ExpressionTypeError('表达式 text 至少需要一个参数') 80 | 81 | text = args[0] 82 | if len(args) > 1: 83 | attrs = get_attrs(args[1:]) 84 | else: 85 | attrs = {} 86 | 87 | return { 88 | 'text': text, 89 | 'attrs': attrs 90 | } 91 | 92 | 93 | 至此你已经成功编写了一个表达式,载入表达式需要重启工程 94 | 95 | .. note:: 96 | 97 | 函数 ``calculate()`` 并没有限制返回的数据类型,你可以返回字符串、字典或者html标签(在最早版本的表达式中,就是这样做的) 98 | 99 | 不过建议返回字典或字符串,这样更利于主题开发者使用你的表达式返回结果 -------------------------------------------------------------------------------- /docs/developer_guide/expression/format_expression.rst: -------------------------------------------------------------------------------- 1 | .. _format-expression: 2 | 3 | =============== 4 | 解析表达式 5 | =============== 6 | 7 | ``app.deeru_expression.manager`` 定义了一个解析表达式的函数 8 | 9 | 10 | .. py:function:: format_expression(value) 11 | 12 | 返回: 13 | 全局变量表达式返回全局变量字符串, 14 | 15 | 代码表达式返回对应的表达式对象 16 | 17 | 非表达式返回value 18 | 19 | 参数: 20 | value: 表达式字符串 21 | 22 | 23 | 代码表达式返回的是代码表达式的实例化对象,需要在外部调用 ``get_result()`` 获取解析结果 24 | 25 | -------------------------------------------------------------------------------- /docs/developer_guide/expression/index.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | 表达式开发 3 | ==================== 4 | 5 | 如何解析、自定义表达式 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 5 10 | :caption: 目录: 11 | 12 | 13 | format_expression 14 | custom_expression 15 | 16 | -------------------------------------------------------------------------------- /docs/developer_guide/index.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | 开发指南 3 | ==================== 4 | 5 | .. Attention:: 6 | 7 | 注意!!! 你所有的代码都应放在 ``custom_`` 开头的文件夹下 8 | 9 | 10 | .. toctree:: 11 | :hidden: 12 | :maxdepth: 5 13 | 14 | role 15 | theme/index 16 | model/index 17 | config/index 18 | contribution.rst 19 | 20 | 21 | .. topic:: 目录 22 | 23 | * :ref:`role` 24 | * :ref:`theme` 25 | * :ref:`model` 26 | * :ref:`config` 27 | -------------------------------------------------------------------------------- /docs/developer_guide/model/index.rst: -------------------------------------------------------------------------------- 1 | .. _model: 2 | 3 | ============== 4 | Model 5 | ============== 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 5 10 | :caption: 目录: 11 | 12 | 13 | content_model 14 | -------------------------------------------------------------------------------- /docs/developer_guide/role.rst: -------------------------------------------------------------------------------- 1 | .. _role: 2 | 3 | ================ 4 | 开发建议 5 | ================ 6 | 7 | #. **代码路径** 8 | 9 | .. attention:: 10 | 11 | 重要!! 你所有的代码都应放在 ``custom_`` 开头的文件夹下 12 | 13 | #. **导入model** 14 | 15 | 你应该从 ``app.app_models`` 中导入,而不是从 ``app.models`` 中,如:: 16 | 17 | from app.app_models.content_model import Article 18 | 19 | #. **Config命名** 20 | 21 | 如果你需要用到config,建议给config取一个本地化语言的名字,而不是英语名。 22 | 你可以用一个dict保存英语名与本地化名,如:: 23 | 24 | config_name={ 25 | 26 | 'top_menu':'顶部导航栏' 27 | } 28 | 29 | # 使用时 30 | 31 | create_config( name=config_name['top_menu'], v2_config='' ) 32 | 33 | config = get_config( name=config_name['top_menu'] ) 34 | 35 | #. **如何获取Config** 36 | 37 | 获取config时,你应该从 ``config.v2_real_config`` 中读取配置,``config.v2_config`` 中的配置是未进handler处理的:: 38 | 39 | from ast import literal_eval 40 | from app.db_manager.config_manager import get_config_by_name 41 | from app.consts import app_config_context 42 | 43 | iconbar_config=get_config_by_name(app_config_context['v2_iconbar_config']) 44 | 45 | print(iconbar.v2_real_config) 46 | 47 | 48 | #. **dispatch_uid** 49 | 50 | 如果你要使用绑定model的信号,需要注意DeerU预定了 "model名_信号名" 格式的 dispatch_uid ,如:"article_pre_save","article_post_save"。 51 | 建议在dispatch_uid前加上你的昵称或包名,防止冲突。 52 | -------------------------------------------------------------------------------- /docs/developer_guide/theme/context.rst: -------------------------------------------------------------------------------- 1 | .. _context: 2 | 3 | ============== 4 | Context 5 | ============== 6 | 7 | context是什么? 8 | ==================== 9 | 渲染html时view会把context传给html模板,context包含了模板需要的变量,数据等(如:文章、分类)。 10 | 11 | 12 | 基础context格式 13 | =================== 14 | 每个view都返回了一个基础的context,他的格式如下:: 15 | 16 | context = { 17 | 18 | 'config' : { 19 | 'v2_iconbar_config' : { ... }, 20 | 'v2_navbar_config' : { ... }, 21 | 'v2_common_config' : [ ... ], 22 | 'v2_blog_config' : { ... } 23 | } 24 | 25 | 'extend_data' : { 26 | 'category' : [ 27 | 28 | { 29 | 'category' : Category, # Category model的实例化对象 30 | 'children' :[ 31 | { 32 | 'category':Category 33 | }, 34 | { ... } 35 | ] 36 | 37 | }, 38 | 39 | { ... } 40 | 41 | ] 42 | 43 | 'tags' :[ Tag, Tag ,] # Tag model的实例化对象 44 | } 45 | } 46 | 47 | * config : 后台的配置,默认返回的配置有 'v2_iconbar_config','v2_navbar_config','v2_common_config','v2_blog_config' 48 | * extend_data : 里面包含了博客的分类,标签 49 | - category : 按父子结构整理后的分类 50 | - tag : 按文章数量排序的tag list,返回20个 51 | 52 | 在html中使用context 53 | ======================== 54 | 55 | 在html中使用context实例 56 | 57 | .. code-block:: html 58 | 59 | 博客名: {{ config.v2_blog_config.title }} 60 | 61 |
    62 | {% for t in tags %} 63 | {{ t.name }} 64 | {% endfor %} 65 |
    66 | 67 | context 中返回的 article, category, tag 等都是对应model的实例化对象,你可以直接在模板中使用对象的成员变量及函数,如 68 | 69 | .. code-block:: html 70 | 71 | # 假设context = { 'article':Article } 72 | 73 | # html: 74 |

    {{ article.title }}

    75 |
    {{ article.content }}
    76 | 77 | # 获取文章的分类 78 | {% for c in article.category %} 79 | {{ c.name }} | 80 | {% end for %} 81 | 82 | DeerU为每个model都提供了丰富的成员函数,你轻易从对象中获取你需要的数据。 83 | 每个model的变量、函数说明参照 :ref:`model` 这里不再叙述。 84 | 85 | 除了model里的对象,context还有一些特殊的对象: 86 | 87 | 88 | .. py:class:: DeerUPaginator 89 | 90 | deeru的Paginator 91 | 92 | .. py:attribute:: end_index 93 | 94 | 末尾页码 95 | 96 | .. py:attribute:: current_page_num 97 | 98 | 当前页码 99 | 100 | 101 | .. py:class:: CommentForm 102 | 103 | 评论的form 104 | 105 | 106 | 在html中读取配置 107 | ========================= 108 | 109 | 如果你看了使用者指南你应该清楚,DeerU内置了"顶部导航栏"、"顶部图标栏"两个配置,你可以在view传到的context['config']中找到他们。 110 | 111 | 在前端代码中,你可以通过 ``config.v2_xxx`` 获取配置(v2.0+的配置以v2_开头),如 :: 112 | 113 |
    博客名: {{ config.v2_blog_config.title }}
    114 | 115 | 如果你还需要其他配置,你可以把配置放到"通用配置"中,你也可以新建一个自己的配置。 116 | 117 | 118 | 每个页面单独的context 119 | ======================== 120 | 121 | 每个页面单独的 context 见 :ref:`page-c-t` 。 122 | -------------------------------------------------------------------------------- /docs/developer_guide/theme/index.rst: -------------------------------------------------------------------------------- 1 | .. _theme: 2 | 3 | ==================== 4 | 自定义主题 5 | ==================== 6 | 7 | v2.1 开始对前端代码进行了重构,使开发主题更容易。 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | theme 13 | context 14 | templates 15 | url_view 16 | other 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/developer_guide/theme/other.rst: -------------------------------------------------------------------------------- 1 | .. _templates: 2 | 3 | ================== 4 | 主题开发的其他说明 5 | ================== 6 | 7 | 8 | 在html中应使用软连接 9 | ===================== 10 | 11 | 如果你需要在html中引入本地静态文件,你应该这样做:: 12 | 13 | {% load static %} 14 | 15 | 16 | 17 | 如果你需要使用文章url或者其他url,你应该这样做:: 18 | 19 | 21 | 22 | 24 | 25 |
    26 | 27 | 28 | 如何在html中读取新建的配置? 29 | ============================== 30 | 31 | 在admin中新建的配置默认在html代码中是无法获取,你需要修改两个地方 32 | 33 | 1. 新建 ``custom_theme/consts.py`` 文件,并添加 ``custom_theme_config_context`` :: 34 | 35 | custom_theme_config_context = { 36 | 'my_config' : '我的自定义配置' 37 | } 38 | 39 | 2. 在 ``custom_theme/app.py`` 中添加 ``deeru_config_context`` :: 40 | 41 | class CustomThemeConfig(AppConfig): 42 | ... 43 | deeru_config_context = 'custom_theme.consts.custom_theme_config_context' 44 | 45 | 46 | 关于评论的form 47 | ==================== 48 | 49 | 文章详情页面传了一个 CommentForm ,但并不建议直接用它来生成form。另外,该form评论中content生成的 ``