腾讯云ubuntu
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-118-generic x86_64)
没有提供root,考虑到ubuntu的不熟练,按常见问题的解决,添加root按密码登陆,避免了权限修改问题。
1.安装Nginx
root@VM-0-4-ubuntu:~# nginx -v
nginx version: nginx/1.14.0 (Ubuntu)
该版本与原网站版本不同,导致设置文件不能使用。同时也使得uwsgi不能使用,出现502网关错误。故改用gunicorn。
Nginx 的配置位于 /etc/nginx/nginx.conf 文件中
user root;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
#upstream django {
#server 0.0.0.0:3400;
#}
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}
然后在 http 配置下有一个 server 模块,server 模块用于配置一个虚拟服务,使这个虚拟服务监听指定的端口和域名。你可以配置多个 server,这样就会启动多个虚拟服务,用于监听不同端口,或者是同一个端口,但是不同的域名,这样你就可以在同一服务器部署多个 web 应用了。
这个 server 的配置我们下面会详细讲解,再来看看 server 下的 include,include 会将指定路径中配置文件包含进来,这样便于配置的模块化管理,例如我们可以把不同 web 应用的配置放到 /etc/nginx/conf.d/ 目录下,这样 nginx 会把这个目录下所有以 .conf 结尾的文件内容包含到 nginx.conf 的配置中来,而无需把所有配置都堆到 nginx.conf 中,使得配置文件十分臃肿。
在/etc/nginx/conf.d下放置我们的server配置
# mysite_nginx.conf
# configuration of the server
server {
# the port your site will be served on
listen 80;
root /home/ubuntu/hnuliulinhai/blogproject;
# the domain name it will serve for
server_name www.summerdawn.top www.summerdawn.xyz summerdawn.top summerdawn.xyz www.zhonghaitec.cn zhonghaitec.cn 81.69.226.61 127.0.0.1; # substitute your machine's IP address or FQDN
charset utf-8;
# max upload size
client_max_body_size 300M; # adjust to taste
# Django media
location /media {
alias /home/ubuntu/hnuliulinhai/blogproject/media; # your Django project's media files - amend as required
}
location /static {
alias /home/ubuntu/hnuliulinhai/blogproject/static; # your Django project's static files - amend as required
}
location ~ ^/favicon\.ico$ {
root /home/ubuntu/hnuliulinhai/blogproject;
}
# Finally, send all non-media requests to the Django server.
location / {
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8000;
#uwsgi_pass django;
#include /etc/nginx/uwsgi_params; # the uwsgi_params file you installed
}
}
nginx重启不在赘述
2.安装gunicorn
在项目文件下
cd /home/ubuntu/hnuliulinhai/blogproject
pipenv run gunicorn blogproject.wsgi -w 2 -k gthread -b 127.0.0.1:8000
查看进程和杀死进程
pstree -ap|grep gunicorn
kill -9 xxxx
3.使用pipenv
pip3 install pipenv
在项目文件夹下
cd /home/ubuntu/hnuliulinhai/blogproject
使用pipenv install
在pipenv命令下,安装django 2.1.10、django-contrib-comments、django-qiniu-strorage、django-ckeditor 5.9.0
pipenv install django-ckeditor==5.9.0
所有包装好后,pipenv graph后的结果:
pipenv graph
django-ckeditor==5.9.0
- django-js-asset [required: >=1.2.2, installed: 1.2.2]
django-contrib-comments==2.0.0
- Django [required: >=1.11, installed: 2.1.10]
- pytz [required: Any, installed: 2020.5]
django-qiniu-storage==2.3.1
- qiniu [required: >=7.1.0, installed: 7.3.1]
- requests [required: Any, installed: 2.25.1]
- certifi [required: >=2017.4.17, installed: 2020.12.5]
- chardet [required: >=3.0.2,<5, installed: 4.0.0]
- idna [required: >=2.5,<3, installed: 2.10]
- urllib3 [required: >=1.21.1,<1.27, installed: 1.26.2]
- requests [required: Any, installed: 2.25.1]
- certifi [required: >=2017.4.17, installed: 2020.12.5]
- chardet [required: >=3.0.2,<5, installed: 4.0.0]
- idna [required: >=2.5,<3, installed: 2.10]
- urllib3 [required: >=1.21.1,<1.27, installed: 1.26.2]
- six [required: Any, installed: 1.15.0]
gunicorn==20.0.4
- setuptools [required: >=3.0, installed: 51.1.2]
Pillow==8.1.0
注意django-ckeditor 6.0.0不支持2.2以下版本,导致我调试多时。
pipenv run python3 manage.py collectstatic
pipenv run python3 manage.py runserver 0.0.0.0:8000
4.使用supervisor
参照追梦人物的介绍:
为了方便,我一般会设置如下的目录结构(位于 ~/etc 目录下)来管理 Supervisor 有关的文件:
~/etc ├── supervisor │ ├── conf.d │ └── var │ ├── log └── supervisord.conf
其中 supervisord.conf 是 Supervior 的配置文件,它会包含 conf.d 下的配置。var 目录下用于存放一些经常变动的文件,例如 socket 文件,pid 文件,log 下则存放日志文件。
首先来建立上述的目录结构:
yangxg@server:$ mkdir -p ~/etc/supervisor/conf.d yangxg@server:$ mkdir -p ~/etc/supervisor/var/log
然后进入 ~/etc 目录下生成 Supervisor 的配置文件:
yangxg@server:$ cd ~/etc yangxg@server:$ echo_supervisord_conf > supervisord.conf
修改 supervisor.conf,让 Supervisor 进程产生的一些文件生成到上面我们创建的目录下,而不是其默认指定的地方。
首先找到 [unix_http_server] 版块,将 file 设置改为如下的值:
[unix_http_server] file=/home/yangxg/etc/supervisor/var/supervisor.sock
即让 socket 文件生成在 ~/etc/supervisor/var/ 目录下。注意 supervisor 不支持将 ~ 展开为用户 home 目录,所以要用绝对路径指定。
类似的修改 [supervisord] 板块下的 logfile 和 pidfile 文件的路径,还有 user 改为系统用户,这样 supervisor 启动的进程将以系统用户运行,避免可能的权限问题:
logfile=/home/yangxg/etc/supervisor/var/log/supervisord.log pidfile=/home/yangxg/etc/supervisor/var/supervisord.pid user=yangxg
还有 [supervisorctl] 板块下:
serverurl=unix:///home/yangxg/etc/supervisor/var/supervisor.sock
[include] 版块,将 /home/yangxg/etc/supervisor/conf.d/ 目录下所有以 .ini 结尾的文件内容包含到配置中来,这样便于配置的模块化管理,和之前 Nginx 配置文件的处理方式是类似的。
files = /home/yangxg/etc/supervisor/conf.d/*.ini
然后我们到 conf.d 新建我们博客应用的配置:
[program:hellodjango-blog-tutorial] command=pipenv run gunicorn blogproject.wsgi -w 2 -k gthread -b 127.0.0.1:8000 directory=/home/yangxg/apps/HelloDjango-blog-tutorial autostart=true autorestart=unexpected user=yangxg stdout_logfile=/home/yangxg/etc/supervisor/var/log/hellodjango-blog-tutorial-stdout.log stderr_logfile=/home/yangxg/etc/supervisor/var/log/hellodjango-blog-tutorial-stderr.log
说一下各项配置的含义:
[program:hellodjango-blog-tutorial] 指明运行应用的进程,名为 hellodjango-blog-tutorial。
command 为进程启动时执行的命令。
directory 指定执行命令时所在的目录。
autostart 随 Supervisor 启动自动启动进程。
autorestart 进程意外退出时重启。
user 进程运行的用户,防止权限问题。
stdout_logfile,stderr_logfile 日志输出文件。
启动 Supervisor
yangxg@server:$ supervisord -c ~/etc/supervisord.conf
-c 指定 Supervisr 启动时的配置文件。
进入 supervisorctl 进程管理控制台:
yangxg@server:$ supervisorctl -c ~/etc/supervisord.conf
执行 update 命令更新配置文件并启动应用。
其他不再赘述
5.补充评论迁移
补充自2021年11月27日,实际上评论功能也需要迁移。评论是在django-comments中修改文件。修改内容以前有summerdawn文章评论调整一文的介绍,本次考虑便捷性,将修改文件内容补充如下:
5.1文件\site-packages\django_comments\abstracts.py的模型model需增加三个字段
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .managers import CommentManager
COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
class BaseCommentAbstractModel(models.Model):
"""
An abstract base class that any custom comment models probably should
subclass.
"""
# Content-object field
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s",
on_delete=models.CASCADE)
object_pk = models.TextField(_('object ID'))
content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
# Metadata about the comment
site = models.ForeignKey(Site, on_delete=models.CASCADE)
class Meta:
abstract = True
def get_content_object_url(self):
"""
Get a URL suitable for redirecting to the content object.
"""
return reverse(
"comments-url-redirect",
args=(self.content_type_id, self.object_pk)
)
class CommentAbstractModel(BaseCommentAbstractModel):
"""
A user comment about some object.
"""
# Who posted this comment? If ``user`` is set then it was an authenticated
# user; otherwise at least user_name should have been set and the comment
# was posted by a non-authenticated user.
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
blank=True, null=True, related_name="%(class)s_comments",
on_delete=models.SET_NULL)
user_name = models.CharField(_("user's name"), max_length=50, blank=True)
user_email = models.EmailField(_("user's email address"), blank=True)
user_url = models.URLField(_("user's URL"), blank=True)
comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)
# Metadata about the comment
submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)
ip_address = models.GenericIPAddressField(_('IP address'), unpack_ipv4=True, blank=True, null=True)
is_public = models.BooleanField(_('is public'), default=True,
help_text=_('Uncheck this box to make the comment effectively '
'disappear from the site.'))
is_removed = models.BooleanField(_('is removed'), default=False,
help_text=_('Check this box if the comment is inappropriate. '
'A "This comment has been removed" message will '
'be displayed instead.'))
#修改2018年2月9日
root_id =models.IntegerField(default=0)
reply_to =models.IntegerField(default=0)
reply_name=models.CharField(max_length=50,blank=True)
# Manager
objects = CommentManager()
class Meta:
abstract = True
ordering = ('submit_date',)
permissions = [("can_moderate", "Can moderate comments")]
verbose_name = _('comment')
verbose_name_plural = _('comments')
def __str__(self):
return "%s: %s..." % (self.name, self.comment[:50])
def save(self, *args, **kwargs):
if self.submit_date is None:
self.submit_date = timezone.now()
super().save(*args, **kwargs)
def _get_userinfo(self):
"""
Get a dictionary that pulls together information about the poster
safely for both authenticated and non-authenticated comments.
This dict will have ``name``, ``email``, and ``url`` fields.
"""
if not hasattr(self, "_userinfo"):
userinfo = {
"name": self.user_name,
"email": self.user_email,
"url": self.user_url
}
if self.user_id:
u = self.user
if u.email:
userinfo["email"] = u.email
# If the user has a full name, use that for the user name.
# However, a given user_name overrides the raw user.username,
# so only use that if this comment has no associated name.
if u.get_full_name():
userinfo["name"] = self.user.get_full_name()
elif not self.user_name:
userinfo["name"] = u.get_username()
self._userinfo = userinfo
return self._userinfo
userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__)
def _get_name(self):
return self.userinfo["name"]
def _set_name(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "
"user and thus the name is read-only."))
self.user_name = val
name = property(_get_name, _set_name, doc="The name of the user who posted this comment")
def _get_email(self):
return self.userinfo["email"]
def _set_email(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "
"user and thus the email is read-only."))
self.user_email = val
email = property(_get_email, _set_email, doc="The email of the user who posted this comment")
def _get_url(self):
return self.userinfo["url"]
def _set_url(self, val):
self.user_url = val
url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")
def get_absolute_url(self, anchor_pattern="#c%(id)s"):
return self.get_content_object_url() + (anchor_pattern % self.__dict__)
def get_as_text(self):
"""
Return this comment as plain text. Useful for emails.
"""
d = {
'user': self.user or self.name,
'date': self.submit_date,
'comment': self.comment,
'domain': self.site.domain,
'url': self.get_absolute_url()
}
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
5.2控制views/comments需要处理相应字段,同时修改支持ajax
from django import http
from django.apps import apps
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.html import escape
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST
import django_comments
from django_comments import signals
from django_comments.views.utils import next_redirect, confirmation_view
from django.http import JsonResponse
class CommentPostBadRequest(http.HttpResponseBadRequest):
"""
Response returned when a comment post is invalid. If ``DEBUG`` is on a
nice-ish error message will be displayed (for debugging purposes), but in
production mode a simple opaque 400 page will be displayed.
"""
def __init__(self, why):
super().__init__()
if settings.DEBUG:
self.content = render_to_string("comments/400-debug.html", {"why": why})
@csrf_protect
@require_POST
def post_comment(request, next=None, using=None):
"""
Post a comment.
HTTP POST is required. If ``POST['submit'] == "preview"`` or if there are
errors a preview template, ``comments/preview.html``, will be rendered.
"""
# Fill out some initial data fields from an authenticated user, if present
data = request.POST.copy()
if request.user.is_authenticated:
if not data.get('name', ''):
data["name"] = request.user.get_full_name() or request.user.get_username()
if not data.get('email', ''):
data["email"] = request.user.email
#修改2021年11月19日
else:
return JsonResponse({'code':501, 'success':False, 'message':'No Login'})
# Look up the object we're trying to comment about
ctype = data.get("content_type")
object_pk = data.get("object_pk")
if ctype is None or object_pk is None:
#return CommentPostBadRequest("Missing content_type or object_pk field.")
return JsonResponse({'code':502, 'success':False, 'message':"Missing content_type or object_pk field."})
try:
model = apps.get_model(*ctype.split(".", 1))
target = model._default_manager.using(using).get(pk=object_pk)
except TypeError:
return CommentPostBadRequest(
"Invalid content_type value: %r" % escape(ctype))
except AttributeError:
return CommentPostBadRequest(
"The given content-type %r does not resolve to a valid model." % escape(ctype))
except ObjectDoesNotExist:
return CommentPostBadRequest(
"No object matching content-type %r and object PK %r exists." % (
escape(ctype), escape(object_pk)))
except (ValueError, ValidationError) as e:
return CommentPostBadRequest(
"Attempting go get content-type %r and object PK %r exists raised %s" % (
escape(ctype), escape(object_pk), e.__class__.__name__))
# Do we want to preview the comment?
preview = "preview" in data
# Construct the comment form
form = django_comments.get_form()(target, data=data)
# Check security information
if form.security_errors():
#return CommentPostBadRequest(
# "The comment form failed security verification: %s" % escape(str(form.security_errors())))
return JsonResponse({'code': 503, 'success': False, 'message':
"The comment form failed security verification: %s" % escape(str(form.security_errors()))})
# If there are errors or if we requested a preview show the comment
if form.errors or preview:
template_list = [
# These first two exist for purely historical reasons.
# Django v1.0 and v1.1 allowed the underscore format for
# preview templates, so we have to preserve that format.
"comments/%s_%s_preview.html" % (model._meta.app_label, model._meta.model_name),
"comments/%s_preview.html" % model._meta.app_label,
# Now the usual directory based template hierarchy.
"comments/%s/%s/preview.html" % (model._meta.app_label, model._meta.model_name),
"comments/%s/preview.html" % model._meta.app_label,
"comments/preview.html",
]
# return render(request, template_list, {
# "comment": form.data.get("comment", ""),
# "form": form,
# "next": data.get("next", next),
# },
# )
# 修改2021年11月19日
return JsonResponse({'code': 502, 'success': False, 'message': form.data.get("comment", "")})
# Otherwise create the comment
comment = form.get_comment_object(site_id=get_current_site(request).id)
comment.ip_address = request.META.get("REMOTE_ADDR", None) or None
#增加 2018年2月10日
comment.root_id = data.get('root_id',0)
comment.reply_to = data.get('reply_to',0)
comment.reply_name = data.get('reply_name','')
if request.user.is_authenticated:
comment.user = request.user
# Signal that the comment is about to be saved
responses = signals.comment_will_be_posted.send(
sender=comment.__class__,
comment=comment,
request=request
)
for (receiver, response) in responses:
if response is False:
return CommentPostBadRequest(
"comment_will_be_posted receiver %r killed the comment" % receiver.__name__)
# Save the comment and signal that it was saved
comment.save()
signals.comment_was_posted.send(
sender=comment.__class__,
comment=comment,
request=request
)
# return next_redirect(request, fallback=next or 'comments-comment-done',
# c=comment._get_pk_val())
#修改2021年11月19日
return JsonResponse({'code':200, 'success':True, 'message':'comment suceess!'})
comment_done = confirmation_view(
template="comments/posted.html",
doc="""Display a "comment was posted" success page."""
)
5.3修改自定义标签comments,为视图提供内容:/site-packages/django_comments/templatetags/comments.py中,修改get_queryset函数
from django import template
from django.template.loader import render_to_string
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.shortcuts import get_current_site
from django.utils.encoding import smart_str
import django_comments
register = template.Library()
class BaseCommentNode(template.Node):
"""
Base helper class (abstract) for handling the get_comment_* template tags.
Looks a bit strange, but the subclasses below should make this a bit more
obvious.
"""
@classmethod
def handle_token(cls, parser, token):
"""Class method to parse get_comment_list/count/form and return a Node."""
tokens = token.split_contents()
if tokens[1] != 'for':
raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
# {% get_whatever for obj as varname %}
if len(tokens) == 5:
if tokens[3] != 'as':
raise template.TemplateSyntaxError("Third argument in %r must be 'as'" % tokens[0])
return cls(
object_expr=parser.compile_filter(tokens[2]),
as_varname=tokens[4],
)
# {% get_whatever for app.model pk as varname %}
elif len(tokens) == 6:
if tokens[4] != 'as':
raise template.TemplateSyntaxError("Fourth argument in %r must be 'as'" % tokens[0])
return cls(
ctype=BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
object_pk_expr=parser.compile_filter(tokens[3]),
as_varname=tokens[5]
)
else:
raise template.TemplateSyntaxError("%r tag requires 4 or 5 arguments" % tokens[0])
@staticmethod
def lookup_content_type(token, tagname):
try:
app, model = token.split('.')
return ContentType.objects.get_by_natural_key(app, model)
except ValueError:
raise template.TemplateSyntaxError("Third argument in %r must be in the format 'app.model'" % tagname)
except ContentType.DoesNotExist:
raise template.TemplateSyntaxError("%r tag has non-existant content-type: '%s.%s'" % (tagname, app, model))
def __init__(self, ctype=None, object_pk_expr=None, object_expr=None, as_varname=None, comment=None):
if ctype is None and object_expr is None:
raise template.TemplateSyntaxError(
"Comment nodes must be given either a literal object or a ctype and object pk.")
self.comment_model = django_comments.get_model()
self.as_varname = as_varname
self.ctype = ctype
self.object_pk_expr = object_pk_expr
self.object_expr = object_expr
self.comment = comment
def render(self, context):
qs = self.get_queryset(context)
context[self.as_varname] = self.get_context_value_from_queryset(context, qs)
return ''
def get_queryset(self, context):
ctype, object_pk = self.get_target_ctype_pk(context)
if not object_pk:
return self.comment_model.objects.none()
# Explicit SITE_ID takes precedence over request. This is also how
# get_current_site operates.
site_id = getattr(settings, "SITE_ID", None)
if not site_id and ('request' in context):
site_id = get_current_site(context['request']).pk
qs = self.comment_model.objects.filter(
content_type=ctype,
object_pk=smart_str(object_pk),
site__pk=site_id,
root_id=0, # 增加2018年2月10日
)
# The is_public and is_removed fields are implementation details of the
# built-in comment model's spam filtering system, so they might not
# be present on a custom comment model subclass. If they exist, we
# should filter on them.
field_names = [f.name for f in self.comment_model._meta.fields]
if 'is_public' in field_names:
qs = qs.filter(is_public=True)
if getattr(settings, 'COMMENTS_HIDE_REMOVED', True) and 'is_removed' in field_names:
qs = qs.filter(is_removed=False)
if 'user' in field_names:
qs = qs.select_related('user')
# 增加2018年2月10日
for q in qs:
q.replies = self.comment_model.objects.filter(
content_type=ctype,
object_pk=smart_str(object_pk),
site__pk=site_id,
root_id=q.id,
is_public=True,
is_removed=False,
).order_by('submit_date')
# 动态增加replies属性
return qs
def get_target_ctype_pk(self, context):
if self.object_expr:
try:
obj = self.object_expr.resolve(context)
except template.VariableDoesNotExist:
return None, None
return ContentType.objects.get_for_model(obj), obj.pk
else:
return self.ctype, self.object_pk_expr.resolve(context, ignore_failures=True)
def get_context_value_from_queryset(self, context, qs):
"""Subclasses should override this."""
raise NotImplementedError
class CommentListNode(BaseCommentNode):
"""Insert a list of comments into the context."""
def get_context_value_from_queryset(self, context, qs):
return qs
class CommentCountNode(BaseCommentNode):
"""Insert a count of comments into the context."""
def get_context_value_from_queryset(self, context, qs):
return qs.count()
class CommentFormNode(BaseCommentNode):
"""Insert a form for the comment model into the context."""
def get_form(self, context):
obj = self.get_object(context)
if obj:
return django_comments.get_form()(obj)
else:
return None
def get_object(self, context):
if self.object_expr:
try:
return self.object_expr.resolve(context)
except template.VariableDoesNotExist:
return None
else:
object_pk = self.object_pk_expr.resolve(context,
ignore_failures=True)
return self.ctype.get_object_for_this_type(pk=object_pk)
def render(self, context):
context[self.as_varname] = self.get_form(context)
return ''
class RenderCommentFormNode(CommentFormNode):
"""Render the comment form directly"""
@classmethod
def handle_token(cls, parser, token):
"""Class method to parse render_comment_form and return a Node."""
tokens = token.split_contents()
if tokens[1] != 'for':
raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
# {% render_comment_form for obj %}
if len(tokens) == 3:
return cls(object_expr=parser.compile_filter(tokens[2]))
# {% render_comment_form for app.models pk %}
elif len(tokens) == 4:
return cls(
ctype=BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
object_pk_expr=parser.compile_filter(tokens[3])
)
def render(self, context):
ctype, object_pk = self.get_target_ctype_pk(context)
if object_pk:
template_search_list = [
"comments/%s/%s/form.html" % (ctype.app_label, ctype.model),
"comments/%s/form.html" % ctype.app_label,
"comments/form.html"
]
context_dict = context.flatten()
context_dict['form'] = self.get_form(context)
formstr = render_to_string(template_search_list, context_dict)
return formstr
else:
return ''
class RenderCommentListNode(CommentListNode):
"""Render the comment list directly"""
@classmethod
def handle_token(cls, parser, token):
"""Class method to parse render_comment_list and return a Node."""
tokens = token.split_contents()
if tokens[1] != 'for':
raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
# {% render_comment_list for obj %}
if len(tokens) == 3:
return cls(object_expr=parser.compile_filter(tokens[2]))
# {% render_comment_list for app.models pk %}
elif len(tokens) == 4:
return cls(
ctype=BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
object_pk_expr=parser.compile_filter(tokens[3])
)
def render(self, context):
ctype, object_pk = self.get_target_ctype_pk(context)
if object_pk:
template_search_list = [
"comments/%s/%s/list.html" % (ctype.app_label, ctype.model),
"comments/%s/list.html" % ctype.app_label,
"comments/list.html"
]
qs = self.get_queryset(context)
context_dict = context.flatten()
context_dict['comment_list'] = self.get_context_value_from_queryset(context, qs)
liststr = render_to_string(template_search_list, context_dict)
return liststr
else:
return ''
# We could just register each classmethod directly, but then we'd lose out on
# the automagic docstrings-into-admin-docs tricks. So each node gets a cute
# wrapper function that just exists to hold the docstring.
@register.tag
def get_comment_count(parser, token):
"""
Gets the comment count for the given params and populates the template
context with a variable containing that value, whose name is defined by the
'as' clause.
Syntax::
{% get_comment_count for [object] as [varname] %}
{% get_comment_count for [app].[model] [object_id] as [varname] %}
Example usage::
{% get_comment_count for event as comment_count %}
{% get_comment_count for calendar.event event.id as comment_count %}
{% get_comment_count for calendar.event 17 as comment_count %}
"""
return CommentCountNode.handle_token(parser, token)
@register.tag
def get_comment_list(parser, token):
"""
Gets the list of comments for the given params and populates the template
context with a variable containing that value, whose name is defined by the
'as' clause.
Syntax::
{% get_comment_list for [object] as [varname] %}
{% get_comment_list for [app].[model] [object_id] as [varname] %}
Example usage::
{% get_comment_list for event as comment_list %}
{% for comment in comment_list %}
...
{% endfor %}
"""
return CommentListNode.handle_token(parser, token)
@register.tag
def render_comment_list(parser, token):
"""
Render the comment list (as returned by ``{% get_comment_list %}``)
through the ``comments/list.html`` template
Syntax::
{% render_comment_list for [object] %}
{% render_comment_list for [app].[model] [object_id] %}
Example usage::
{% render_comment_list for event %}
"""
return RenderCommentListNode.handle_token(parser, token)
@register.tag
def get_comment_form(parser, token):
"""
Get a (new) form object to post a new comment.
Syntax::
{% get_comment_form for [object] as [varname] %}
{% get_comment_form for [app].[model] [object_id] as [varname] %}
"""
return CommentFormNode.handle_token(parser, token)
@register.tag
def render_comment_form(parser, token):
"""
Render the comment form (as returned by ``{% render_comment_form %}``) through
the ``comments/form.html`` template.
Syntax::
{% render_comment_form for [object] %}
{% render_comment_form for [app].[model] [object_id] %}
"""
return RenderCommentFormNode.handle_token(parser, token)
@register.simple_tag
def comment_form_target():
"""
Get the target URL for the comment form.
Example::
<form action="{% comment_form_target %}" method="post">
"""
return django_comments.get_form_target()
@register.simple_tag
def get_comment_permalink(comment, anchor_pattern=None):
"""
Get the permalink for a comment, optionally specifying the format of the
named anchor to be appended to the end of the URL.
Example::
{% get_comment_permalink comment "#c%(id)s-by-%(user_name)s" %}
"""
if anchor_pattern:
return comment.get_absolute_url(anchor_pattern)
return comment.get_absolute_url()
5.4最为重要的是,除去email的验证:为了不验证邮箱,同时评论并未开放邮箱填写,故需要在forms.py文件的class CommentDetailsForm(CommentSecurityForm)类定义中,给email字段添加required=False的要求
import time
from django import forms
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.forms.utils import ErrorDict
from django.utils.crypto import salted_hmac, constant_time_compare
from django.utils.encoding import force_str
from django.utils.text import get_text_list
from django.utils import timezone
from django.utils.translation import pgettext_lazy, ngettext, gettext, gettext_lazy as _
from . import get_model
COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
DEFAULT_COMMENTS_TIMEOUT = getattr(settings, 'COMMENTS_TIMEOUT', (2 * 60 * 60)) # 2h
class CommentSecurityForm(forms.Form):
"""
Handles the security aspects (anti-spoofing) for comment forms.
"""
content_type = forms.CharField(widget=forms.HiddenInput)
object_pk = forms.CharField(widget=forms.HiddenInput)
timestamp = forms.IntegerField(widget=forms.HiddenInput)
security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput)
def __init__(self, target_object, data=None, initial=None, **kwargs):
self.target_object = target_object
if initial is None:
initial = {}
initial.update(self.generate_security_data())
super().__init__(data=data, initial=initial, **kwargs)
def security_errors(self):
"""Return just those errors associated with security"""
errors = ErrorDict()
for f in ["honeypot", "timestamp", "security_hash"]:
if f in self.errors:
errors[f] = self.errors[f]
return errors
def clean_security_hash(self):
"""Check the security hash."""
security_hash_dict = {
'content_type': self.data.get("content_type", ""),
'object_pk': self.data.get("object_pk", ""),
'timestamp': self.data.get("timestamp", ""),
}
expected_hash = self.generate_security_hash(**security_hash_dict)
actual_hash = self.cleaned_data["security_hash"]
if not constant_time_compare(expected_hash, actual_hash):
raise forms.ValidationError("Security hash check failed.")
return actual_hash
def clean_timestamp(self):
"""Make sure the timestamp isn't too far (default is > 2 hours) in the past."""
ts = self.cleaned_data["timestamp"]
if time.time() - ts > DEFAULT_COMMENTS_TIMEOUT:
raise forms.ValidationError("Timestamp check failed")
return ts
def generate_security_data(self):
"""Generate a dict of security data for "initial" data."""
timestamp = int(time.time())
security_dict = {
'content_type': str(self.target_object._meta),
'object_pk': str(self.target_object._get_pk_val()),
'timestamp': str(timestamp),
'security_hash': self.initial_security_hash(timestamp),
}
return security_dict
def initial_security_hash(self, timestamp):
"""
Generate the initial security hash from self.content_object
and a (unix) timestamp.
"""
initial_security_dict = {
'content_type': str(self.target_object._meta),
'object_pk': str(self.target_object._get_pk_val()),
'timestamp': str(timestamp),
}
return self.generate_security_hash(**initial_security_dict)
def generate_security_hash(self, content_type, object_pk, timestamp):
"""
Generate a HMAC security hash from the provided info.
"""
info = (content_type, object_pk, timestamp)
key_salt = "django.contrib.forms.CommentSecurityForm"
value = "-".join(info)
return salted_hmac(key_salt, value).hexdigest()
class CommentDetailsForm(CommentSecurityForm):
"""
Handles the specific details of the comment (name, comment, etc.).
"""
name = forms.CharField(label=pgettext_lazy("Person name", "Name"), max_length=50)
email = forms.EmailField(label=_("Email address"),required=False)
url = forms.URLField(label=_("URL"), required=False)
# Translators: 'Comment' is a noun here.
comment = forms.CharField(label=_('Comment'), widget=forms.Textarea,
max_length=COMMENT_MAX_LENGTH)
def get_comment_object(self, site_id=None):
"""
Return a new (unsaved) comment object based on the information in this
form. Assumes that the form is already validated and will throw a
ValueError if not.
Does not set any of the fields that would come from a Request object
(i.e. ``user`` or ``ip_address``).
"""
if not self.is_valid():
raise ValueError("get_comment_object may only be called on valid forms")
CommentModel = self.get_comment_model()
new = CommentModel(**self.get_comment_create_data(site_id=site_id))
new = self.check_for_duplicate_comment(new)
return new
def get_comment_model(self):
"""
Get the comment model to create with this form. Subclasses in custom
comment apps should override this, get_comment_create_data, and perhaps
check_for_duplicate_comment to provide custom comment models.
"""
return get_model()
def get_comment_create_data(self, site_id=None):
"""
Returns the dict of data to be used to create a comment. Subclasses in
custom comment apps that override get_comment_model can override this
method to add extra fields onto a custom comment model.
"""
return dict(
content_type=ContentType.objects.get_for_model(self.target_object),
object_pk=force_str(self.target_object._get_pk_val()),
user_name=self.cleaned_data["name"],
user_email=self.cleaned_data["email"],
user_url=self.cleaned_data["url"],
comment=self.cleaned_data["comment"],
submit_date=timezone.now(),
site_id=site_id or getattr(settings, "SITE_ID", None),
is_public=True,
is_removed=False,
)
def check_for_duplicate_comment(self, new):
"""
Check that a submitted comment isn't a duplicate. This might be caused
by someone posting a comment twice. If it is a dup, silently return the *previous* comment.
"""
possible_duplicates = self.get_comment_model()._default_manager.using(
self.target_object._state.db
).filter(
content_type=new.content_type,
object_pk=new.object_pk,
user_name=new.user_name,
user_email=new.user_email,
user_url=new.user_url,
)
for old in possible_duplicates:
if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment:
return old
return new
def clean_comment(self):
"""
If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't
contain anything in PROFANITIES_LIST.
"""
comment = self.cleaned_data["comment"]
if (not getattr(settings, 'COMMENTS_ALLOW_PROFANITIES', False) and
getattr(settings, 'PROFANITIES_LIST', False)):
bad_words = [w for w in settings.PROFANITIES_LIST if w in comment.lower()]
if bad_words:
raise forms.ValidationError(ngettext(
"Watch your mouth! The word %s is not allowed here.",
"Watch your mouth! The words %s are not allowed here.",
len(bad_words)) % get_text_list(
['"%s%s%s"' % (i[0], '-' * (len(i) - 2), i[-1])
for i in bad_words], gettext('and')))
return comment
class CommentForm(CommentDetailsForm):
honeypot = forms.CharField(required=False,
label=_('If you enter anything in this field '
'your comment will be treated as spam'))
def clean_honeypot(self):
"""Check that nothing's been entered into the honeypot."""
value = self.cleaned_data["honeypot"]
if value:
raise forms.ValidationError(self.fields["honeypot"].label)
return value
Last Modified·2021年11月27日 21:11
您尚未登录,请先登录才能评论。