编写了详细注释

master
arknights8037 4 months ago
parent 9c5360a6a3
commit 12fd9b9d60

1
src/.gitignore vendored

@ -77,3 +77,4 @@ uploads/
settings_production.py
werobot_session.db
bin/datas/
myenv/

@ -4,57 +4,80 @@ from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.forms import UsernameField
from django.utils.translation import gettext_lazy as _
# Register your models here.
# 注册模型到Django管理后台
from .models import BlogUser
# 自定义用户创建表单用于在Django管理后台创建新用户
class BlogUserCreationForm(forms.ModelForm):
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
# 定义两个密码字段,分别用于输入密码和确认密码
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) # 密码输入框
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) # 确认密码输入框
class Meta:
# 指定表单对应的模型
model = BlogUser
# 表单中包含的字段
fields = ('email',)
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
"""
验证两个密码输入是否一致
"""
password1 = self.cleaned_data.get("password1") # 获取第一个密码
password2 = self.cleaned_data.get("password2") # 获取第二个密码
if password1 and password2 and password1 != password2:
# 如果两个密码不一致,抛出验证错误
raise forms.ValidationError(_("passwords do not match"))
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
"""
保存用户实例并将密码以哈希格式存储
"""
user = super().save(commit=False) # 获取未保存的用户实例
user.set_password(self.cleaned_data["password1"]) # 设置哈希密码
if commit:
user.source = 'adminsite'
user.save()
user.source = 'adminsite' # 设置用户来源为管理后台
user.save() # 保存用户实例到数据库
return user
# 自定义用户修改表单用于在Django管理后台修改用户信息
class BlogUserChangeForm(UserChangeForm):
class Meta:
# 指定表单对应的模型
model = BlogUser
# 表单中包含的所有字段
fields = '__all__'
# 自定义字段类型
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
"""
初始化表单
"""
super().__init__(*args, **kwargs)
# 自定义用户管理类用于在Django管理后台管理用户
class BlogUserAdmin(UserAdmin):
# 指定修改用户信息时使用的表单
form = BlogUserChangeForm
# 指定创建用户时使用的表单
add_form = BlogUserCreationForm
# 定义在用户列表中显示的字段
list_display = (
'id',
'nickname',
'username',
'email',
'last_login',
'date_joined',
'source')
'id', # 用户ID
'nickname', # 昵称
'username', # 用户名
'email', # 邮箱
'last_login', # 上次登录时间
'date_joined', # 注册时间
'source') # 用户来源
# 定义可以点击进入详情页面的字段
list_display_links = ('id', 'username')
# 定义用户列表的排序方式
ordering = ('-id',)
search_fields = ('username', 'nickname', 'email')
# 定义搜索字段
search_fields = ('username', 'nickname', 'email')

@ -6,30 +6,43 @@ from django.utils.translation import gettext_lazy as _
from djangoblog.utils import get_current_site
# Create your models here.
# 创建自定义用户模型
class BlogUser(AbstractUser):
# 用户昵称字段允许为空最大长度为100
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
# 用户创建时间字段,默认为当前时间
creation_time = models.DateTimeField(_('creation time'), default=now)
# 用户最后修改时间字段,默认为当前时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
# 用户来源字段用于记录用户的创建来源允许为空最大长度为100
source = models.CharField(_('create source'), max_length=100, blank=True)
def get_absolute_url(self):
"""
获取用户的绝对URL用于跳转到用户的详情页面
"""
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
'author_name': self.username}) # 根据用户名生成URL
def __str__(self):
"""
定义对象的字符串表示返回用户的邮箱
"""
return self.email
def get_full_url(self):
site = get_current_site().domain
"""
获取用户的完整URL包括域名和路径
"""
site = get_current_site().domain # 获取当前站点的域名
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
path=self.get_absolute_url()) # 拼接完整URL
return url
class Meta:
ordering = ['-id']
verbose_name = _('user')
verbose_name_plural = verbose_name
get_latest_by = 'id'
# 定义模型的元数据
ordering = ['-id'] # 默认按ID降序排列
verbose_name = _('user') # 模型的单数名称
verbose_name_plural = verbose_name # 模型的复数名称
get_latest_by = 'id' # 获取最新记录时使用的字段

@ -1,26 +1,50 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from djangoblog.utils import get_current_site
class EmailOrUsernameModelBackend(ModelBackend):
"""
允许使用用户名或邮箱登录
"""
# 创建自定义用户模型继承自Django内置的AbstractUser
class BlogUser(AbstractUser):
# 用户昵称字段存储用户的昵称允许为空最大长度为100字符
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
# 用户创建时间字段,记录用户的创建时间,默认为当前时间
creation_time = models.DateTimeField(_('creation time'), default=now)
# 用户最后修改时间字段,记录用户信息的最后修改时间,默认为当前时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
# 用户来源字段记录用户的创建来源如管理后台、第三方登录等允许为空最大长度为100字符
source = models.CharField(_('create source'), max_length=100, blank=True)
def authenticate(self, request, username=None, password=None, **kwargs):
if '@' in username:
kwargs = {'email': username}
else:
kwargs = {'username': username}
try:
user = get_user_model().objects.get(**kwargs)
if user.check_password(password):
return user
except get_user_model().DoesNotExist:
return None
def get_absolute_url(self):
"""
获取用户的绝对URL用于跳转到用户的详情页面
例如根据用户名生成用户详情页面的URL
"""
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username}) # 根据用户名生成URL
def get_user(self, username):
try:
return get_user_model().objects.get(pk=username)
except get_user_model().DoesNotExist:
return None
def __str__(self):
"""
定义对象的字符串表示返回用户的邮箱地址
"""
return self.email
def get_full_url(self):
"""
获取用户的完整URL包括域名和路径
例如https://example.com/author/<username>
"""
site = get_current_site().domain # 获取当前站点的域名
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url()) # 拼接完整URL
return url
class Meta:
# 定义模型的元数据
ordering = ['-id'] # 默认按ID降序排列
verbose_name = _('user') # 定义模型在管理后台中的单数名称
verbose_name_plural = verbose_name # 定义模型在管理后台中的复数名称
get_latest_by = 'id' # 指定获取最新记录时使用的字段为ID

@ -1,3 +1,7 @@
# 模块说明:
# 该模块定义了与用户账户相关的视图,包括注册、登录、注销、忘记密码等功能。
# 使用 Django 的类视图和表单视图来处理用户请求。
import logging
from django.utils.translation import gettext_lazy as _
from django.conf import settings
@ -28,23 +32,24 @@ from .models import BlogUser
logger = logging.getLogger(__name__)
# Create your views here.
# 注册视图
class RegisterView(FormView):
# 使用注册表单和模板
form_class = RegisterForm
template_name = 'account/registration_form.html'
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
# 确保请求受 CSRF 保护
return super(RegisterView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
# 表单验证通过后处理注册逻辑
if form.is_valid():
user = form.save(False)
user.is_active = False
user.source = 'Register'
user.save(True)
user = form.save(False) # 创建用户但不保存到数据库
user.is_active = False # 设置用户为未激活状态
user.source = 'Register' # 标记用户来源
user.save(True) # 保存用户到数据库
site = get_current_site().domain
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
@ -70,82 +75,74 @@ class RegisterView(FormView):
],
title='验证您的电子邮箱',
content=content)
url = reverse('accounts:result') + \
'?type=register&id=' + str(user.id)
# 重定向到结果页面
url = reverse('accounts:result') + '?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
else:
return self.render_to_response({
'form': form
})
# 如果表单无效,重新渲染表单
return self.render_to_response({'form': form})
# 注销视图
class LogoutView(RedirectView):
url = '/login/'
url = '/login/' # 注销后重定向到登录页面
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
# 确保注销后页面不被缓存
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
logout(request)
delete_sidebar_cache()
logout(request) # 执行注销操作
delete_sidebar_cache() # 清除侧边栏缓存
return super(LogoutView, self).get(request, *args, **kwargs)
# 登录视图
class LoginView(FormView):
# 使用登录表单和模板
form_class = LoginForm
template_name = 'account/login.html'
success_url = '/'
success_url = '/' # 登录成功后重定向的默认地址
redirect_field_name = REDIRECT_FIELD_NAME
login_ttl = 2626560 # 一个月的时间
login_ttl = 2626560 # 设置会话过期时间为一个月
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
# 确保密码字段敏感、请求受 CSRF 保护且页面不被缓存
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
redirect_to = self.request.GET.get(self.redirect_field_name)
if redirect_to is None:
redirect_to = '/'
# 获取上下文数据,添加重定向地址
redirect_to = self.request.GET.get(self.redirect_field_name, '/')
kwargs['redirect_to'] = redirect_to
return super(LoginView, self).get_context_data(**kwargs)
def form_valid(self, form):
# 表单验证通过后处理登录逻辑
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
delete_sidebar_cache()
logger.info(self.redirect_field_name)
auth.login(self.request, form.get_user())
delete_sidebar_cache() # 清除侧边栏缓存
auth.login(self.request, form.get_user()) # 执行登录操作
if self.request.POST.get("remember"):
self.request.session.set_expiry(self.login_ttl)
self.request.session.set_expiry(self.login_ttl) # 设置会话过期时间
return super(LoginView, self).form_valid(form)
# return HttpResponseRedirect('/')
else:
return self.render_to_response({
'form': form
})
# 如果表单无效,重新渲染表单
return self.render_to_response({'form': form})
def get_success_url(self):
redirect_to = self.request.POST.get(self.redirect_field_name)
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[
self.request.get_host()]):
# 获取登录成功后的重定向地址
redirect_to = self.request.POST.get(self.redirect_field_name, self.success_url)
if not url_has_allowed_host_and_scheme(url=redirect_to, allowed_hosts=[self.request.get_host()]):
redirect_to = self.success_url
return redirect_to
# 账户结果视图
def account_result(request):
# 处理注册或验证结果
type = request.GET.get('type')
id = request.GET.get('id')
user = get_object_or_404(get_user_model(), id=id)
logger.info(type)
if user.is_active:
@ -160,8 +157,8 @@ def account_result(request):
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
sign = request.GET.get('sign')
if sign != c_sign:
return HttpResponseForbidden()
user.is_active = True
return HttpResponseForbidden() # 验证失败返回 403
user.is_active = True # 激活用户
user.save()
content = '''
恭喜您已经成功的完成邮箱验证您现在可以使用您的账号来登录本站
@ -174,31 +171,30 @@ def account_result(request):
else:
return HttpResponseRedirect('/')
# 忘记密码视图
class ForgetPasswordView(FormView):
form_class = ForgetPasswordForm
template_name = 'account/forget_password.html'
def form_valid(self, form):
# 表单验证通过后处理密码重置逻辑
if form.is_valid():
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
blog_user.password = make_password(form.cleaned_data["new_password2"])
blog_user.password = make_password(form.cleaned_data["new_password2"]) # 设置新密码
blog_user.save()
return HttpResponseRedirect('/login/')
return HttpResponseRedirect('/login/') # 重定向到登录页面
else:
return self.render_to_response({'form': form})
# 忘记密码验证码视图
class ForgetPasswordEmailCode(View):
def post(self, request: HttpRequest):
# 处理验证码发送请求
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
return HttpResponse("错误的邮箱")
to_email = form.cleaned_data["email"]
code = generate_code()
utils.send_verify_email(to_email, code)
utils.set_code(to_email, code)
code = generate_code() # 生成验证码
utils.send_verify_email(to_email, code) # 发送验证码邮件
utils.set_code(to_email, code) # 缓存验证码
return HttpResponse("ok")

@ -5,44 +5,55 @@ from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
# Register your models here.
# 导入相关模型
from .models import Article, Category, Tag, Links, SideBar, BlogSettings
# 自定义表单,用于文章模型的管理界面
class ArticleForm(forms.ModelForm):
# body = forms.CharField(widget=AdminPagedownWidget())
# 可以在此处自定义字段的表单小部件
class Meta:
model = Article
fields = '__all__'
# 定义批量操作函数
def makr_article_publish(modeladmin, request, queryset):
# 将选中的文章状态更新为“已发布”
queryset.update(status='p')
def draft_article(modeladmin, request, queryset):
# 将选中的文章状态更新为“草稿”
queryset.update(status='d')
def close_article_commentstatus(modeladmin, request, queryset):
# 将选中的文章评论状态更新为“关闭”
queryset.update(comment_status='c')
def open_article_commentstatus(modeladmin, request, queryset):
# 将选中的文章评论状态更新为“开启”
queryset.update(comment_status='o')
# 为批量操作函数添加描述
makr_article_publish.short_description = _('Publish selected articles')
draft_article.short_description = _('Draft selected articles')
close_article_commentstatus.short_description = _('Close article comments')
open_article_commentstatus.short_description = _('Open article comments')
# 自定义文章管理界面
class ArticlelAdmin(admin.ModelAdmin):
# 每页显示的记录数
list_per_page = 20
# 可搜索的字段
search_fields = ('body', 'title')
# 使用自定义表单
form = ArticleForm
# 列表显示的字段
list_display = (
'id',
'title',
@ -53,19 +64,28 @@ class ArticlelAdmin(admin.ModelAdmin):
'status',
'type',
'article_order')
# 可点击进入详情的字段
list_display_links = ('id', 'title')
# 列表过滤器
list_filter = ('status', 'type', 'category')
# 按日期层级显示
date_hierarchy = 'creation_time'
# 多对多字段的水平过滤器
filter_horizontal = ('tags',)
# 排除的字段
exclude = ('creation_time', 'last_modify_time')
# 是否允许在站点上查看
view_on_site = True
# 批量操作
actions = [
makr_article_publish,
draft_article,
close_article_commentstatus,
open_article_commentstatus]
# 使用原始 ID 字段显示外键
raw_id_fields = ('author', 'category',)
# 自定义显示分类链接
def link_to_category(self, obj):
info = (obj.category._meta.app_label, obj.category._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
@ -73,42 +93,59 @@ class ArticlelAdmin(admin.ModelAdmin):
link_to_category.short_description = _('category')
# 自定义表单的查询集
def get_form(self, request, obj=None, **kwargs):
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
# 限制作者字段仅显示超级用户
form.base_fields['author'].queryset = get_user_model(
).objects.filter(is_superuser=True)
return form
# 保存模型时的自定义逻辑
def save_model(self, request, obj, form, change):
super(ArticlelAdmin, self).save_model(request, obj, form, change)
# 自定义站点查看 URL
def get_view_on_site_url(self, obj=None):
if obj:
# 如果对象存在,返回其完整 URL
url = obj.get_full_url()
return url
else:
# 否则返回当前站点的域名
from djangoblog.utils import get_current_site
site = get_current_site().domain
return site
# 自定义标签管理界面
class TagAdmin(admin.ModelAdmin):
# 排除的字段
exclude = ('slug', 'last_mod_time', 'creation_time')
# 自定义分类管理界面
class CategoryAdmin(admin.ModelAdmin):
# 列表显示的字段
list_display = ('name', 'parent_category', 'index')
# 排除的字段
exclude = ('slug', 'last_mod_time', 'creation_time')
# 自定义链接管理界面
class LinksAdmin(admin.ModelAdmin):
# 排除的字段
exclude = ('last_mod_time', 'creation_time')
# 自定义侧边栏管理界面
class SideBarAdmin(admin.ModelAdmin):
# 列表显示的字段
list_display = ('name', 'content', 'is_enable', 'sequence')
# 排除的字段
exclude = ('last_mod_time', 'creation_time')
# 自定义博客设置管理界面
class BlogSettingsAdmin(admin.ModelAdmin):
pass

@ -7,9 +7,11 @@ from elasticsearch_dsl.connections import connections
from blog.models import Article
# 检查是否启用了 Elasticsearch
ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL')
if ELASTICSEARCH_ENABLED:
# 创建 Elasticsearch 连接
connections.create_connection(
hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']])
from elasticsearch import Elasticsearch
@ -19,8 +21,10 @@ if ELASTICSEARCH_ENABLED:
c = IngestClient(es)
try:
# 检查是否存在名为 'geoip' 的 pipeline
c.get_pipeline('geoip')
except elasticsearch.exceptions.NotFoundError:
# 如果不存在,则创建 'geoip' pipeline
c.put_pipeline('geoip', body='''{
"description" : "Add geoip info",
"processors" : [
@ -33,6 +37,7 @@ if ELASTICSEARCH_ENABLED:
}''')
# 定义 GeoIP 信息的内部文档
class GeoIp(InnerDoc):
continent_name = Keyword()
country_iso_code = Keyword()
@ -40,21 +45,25 @@ class GeoIp(InnerDoc):
location = GeoPoint()
# 定义用户代理浏览器信息的内部文档
class UserAgentBrowser(InnerDoc):
Family = Keyword()
Version = Keyword()
# 定义用户代理操作系统信息的内部文档
class UserAgentOS(UserAgentBrowser):
pass
# 定义用户代理设备信息的内部文档
class UserAgentDevice(InnerDoc):
Family = Keyword()
Brand = Keyword()
Model = Keyword()
# 定义用户代理信息的内部文档
class UserAgent(InnerDoc):
browser = Object(UserAgentBrowser, required=False)
os = Object(UserAgentOS, required=False)
@ -63,6 +72,7 @@ class UserAgent(InnerDoc):
is_bot = Boolean()
# 定义性能日志的 Elasticsearch 文档
class ElapsedTimeDocument(Document):
url = Keyword()
time_taken = Long()
@ -72,6 +82,7 @@ class ElapsedTimeDocument(Document):
useragent = Object(UserAgent, required=False)
class Index:
# 定义索引名称和设置
name = 'performance'
settings = {
"number_of_shards": 1,
@ -82,9 +93,11 @@ class ElapsedTimeDocument(Document):
doc_type = 'ElapsedTime'
# 定义性能日志文档的管理器
class ElaspedTimeDocumentManager:
@staticmethod
def build_index():
# 创建 Elasticsearch 索引
from elasticsearch import Elasticsearch
client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
res = client.indices.exists(index="performance")
@ -93,12 +106,14 @@ class ElaspedTimeDocumentManager:
@staticmethod
def delete_index():
# 删除 Elasticsearch 索引
from elasticsearch import Elasticsearch
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
es.indices.delete(index='performance', ignore=[400, 404])
@staticmethod
def create(url, time_taken, log_datetime, useragent, ip):
# 创建性能日志文档
ElaspedTimeDocumentManager.build_index()
ua = UserAgent()
ua.browser = UserAgentBrowser()
@ -130,6 +145,7 @@ class ElaspedTimeDocumentManager:
doc.save(pipeline="geoip")
# 定义文章的 Elasticsearch 文档
class ArticleDocument(Document):
body = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
title = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
@ -154,6 +170,7 @@ class ArticleDocument(Document):
article_order = Integer()
class Index:
# 定义索引名称和设置
name = 'blog'
settings = {
"number_of_shards": 1,
@ -164,20 +181,25 @@ class ArticleDocument(Document):
doc_type = 'Article'
# 定义文章文档的管理器
class ArticleDocumentManager():
def __init__(self):
# 初始化时创建索引
self.create_index()
def create_index(self):
# 创建 Elasticsearch 索引
ArticleDocument.init()
def delete_index(self):
# 删除 Elasticsearch 索引
from elasticsearch import Elasticsearch
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
es.indices.delete(index='blog', ignore=[400, 404])
def convert_to_doc(self, articles):
# 将文章对象转换为 Elasticsearch 文档
return [
ArticleDocument(
meta={
@ -202,6 +224,7 @@ class ArticleDocumentManager():
article_order=article.article_order) for article in articles]
def rebuild(self, articles=None):
# 重建索引并重新保存文档
ArticleDocument.init()
articles = articles if articles else Article.objects.all()
docs = self.convert_to_doc(articles)
@ -209,5 +232,6 @@ class ArticleDocumentManager():
doc.save()
def update_docs(self, docs):
# 更新文档
for doc in docs:
doc.save()
doc.save()

@ -1,42 +1,71 @@
import logging
import time
from ipware import get_client_ip
from user_agents import parse
from ipware import get_client_ip # 用于获取客户端的 IP 地址
from user_agents import parse # 用于解析用户代理字符串
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager # 导入 Elasticsearch 配置和文档管理器
logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__) # 设置日志记录器
class OnlineMiddleware(object):
def __init__(self, get_response=None):
"""
初始化中间件接收 Django get_response 方法
"""
self.get_response = get_response
super().__init__()
def __call__(self, request):
''' page render time '''
"""
中间件的主要逻辑用于记录页面渲染时间并将其存储到 Elasticsearch
"""
# 记录请求开始时间
start_time = time.time()
# 调用下一个中间件或视图
response = self.get_response(request)
# 获取 HTTP 用户代理字符串
http_user_agent = request.META.get('HTTP_USER_AGENT', '')
# 获取客户端 IP 地址
ip, _ = get_client_ip(request)
# 解析用户代理字符串
user_agent = parse(http_user_agent)
# 如果响应不是流式的,处理渲染时间
if not response.streaming:
try:
# 计算页面渲染时间
cast_time = time.time() - start_time
# 如果启用了 Elasticsearch将数据存储到 Elasticsearch
if ELASTICSEARCH_ENABLED:
time_taken = round((cast_time) * 1000, 2)
url = request.path
time_taken = round((cast_time) * 1000, 2) # 将时间转换为毫秒并保留两位小数
url = request.path # 获取请求的 URL
# 获取当前时间
from django.utils import timezone
# 创建 Elasticsearch 文档
ElaspedTimeDocumentManager.create(
url=url,
time_taken=time_taken,
log_datetime=timezone.now(),
useragent=user_agent,
ip=ip)
ip=ip
)
# 替换响应内容中的占位符 <!!LOAD_TIMES!!> 为渲染时间
response.content = response.content.replace(
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5]))
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5])
)
except Exception as e:
# 如果发生异常,记录错误日志
logger.error("Error OnlineMiddleware: %s" % e)
return response
# 返回响应
return response

@ -4,57 +4,83 @@ from django.views.decorators.cache import cache_page
from . import views
app_name = "blog"
urlpatterns = [
# 首页,显示博客文章列表
path(
r'',
views.IndexView.as_view(),
name='index'),
# 分页的首页,显示指定页码的博客文章列表
path(
r'page/<int:page>/',
views.IndexView.as_view(),
name='index_page'),
# 文章详情页通过年份、月份、日期和文章ID访问
path(
r'article/<int:year>/<int:month>/<int:day>/<int:article_id>.html',
views.ArticleDetailView.as_view(),
name='detailbyid'),
# 分类详情页,通过分类名称访问
path(
r'category/<slug:category_name>.html',
views.CategoryDetailView.as_view(),
name='category_detail'),
# 分类详情页的分页,通过分类名称和页码访问
path(
r'category/<slug:category_name>/<int:page>.html',
views.CategoryDetailView.as_view(),
name='category_detail_page'),
# 作者详情页,通过作者名称访问
path(
r'author/<author_name>.html',
views.AuthorDetailView.as_view(),
name='author_detail'),
# 作者详情页的分页,通过作者名称和页码访问
path(
r'author/<author_name>/<int:page>.html',
views.AuthorDetailView.as_view(),
name='author_detail_page'),
# 标签详情页,通过标签名称访问
path(
r'tag/<slug:tag_name>.html',
views.TagDetailView.as_view(),
name='tag_detail'),
# 标签详情页的分页,通过标签名称和页码访问
path(
r'tag/<slug:tag_name>/<int:page>.html',
views.TagDetailView.as_view(),
name='tag_detail_page'),
# 归档页面显示所有文章的归档信息缓存时间为1小时
path(
'archives.html',
cache_page(
60 * 60)(
60 * 60)( # 缓存时间60分钟 * 60秒
views.ArchivesView.as_view()),
name='archives'),
# 友情链接页面
path(
'links.html',
views.LinkListView.as_view(),
name='links'),
# 文件上传接口
path(
r'upload',
views.fileupload,
name='upload'),
# 清理缓存接口
path(
r'clean',
views.clean_cache_view,

@ -23,7 +23,7 @@ from djangoblog.utils import cache, get_blog_setting, get_sha256
logger = logging.getLogger(__name__)
# 文章列表视图基类
class ArticleListView(ListView):
# template_name属性用于指定使用哪个模板进行渲染
template_name = 'blog/article_index.html'
@ -38,6 +38,7 @@ class ArticleListView(ListView):
link_type = LinkShowType.L
def get_view_cache_key(self):
# 获取视图缓存的键
return self.request.get['pages']
@property
@ -61,7 +62,7 @@ class ArticleListView(ListView):
def get_queryset_from_cache(self, cache_key):
'''
缓存页面数据
缓存中获取页面数据如果不存在则设置缓存
:param cache_key: 缓存key
:return:
'''
@ -77,7 +78,7 @@ class ArticleListView(ListView):
def get_queryset(self):
'''
重写默认从缓存获取数据
重写默认方法从缓存获取数据
:return:
'''
key = self.get_queryset_cache_key()
@ -89,9 +90,10 @@ class ArticleListView(ListView):
return super(ArticleListView, self).get_context_data(**kwargs)
# 首页视图
class IndexView(ArticleListView):
'''
首页
首页文章列表
'''
# 友情链接类型
link_type = LinkShowType.I
@ -105,6 +107,7 @@ class IndexView(ArticleListView):
return cache_key
# 文章详情视图
class ArticleDetailView(DetailView):
'''
文章详情页面
@ -162,9 +165,10 @@ class ArticleDetailView(DetailView):
return context
# 分类目录视图
class CategoryDetailView(ArticleListView):
'''
分类目录列表
分类目录文章列表
'''
page_type = "分类目录归档"
@ -201,9 +205,10 @@ class CategoryDetailView(ArticleListView):
return super(CategoryDetailView, self).get_context_data(**kwargs)
# 作者详情视图
class AuthorDetailView(ArticleListView):
'''
作者详情页
作者文章归档页面
'''
page_type = '作者文章归档'
@ -227,9 +232,10 @@ class AuthorDetailView(ArticleListView):
return super(AuthorDetailView, self).get_context_data(**kwargs)
# 标签详情视图
class TagDetailView(ArticleListView):
'''
标签列表页面
标签文章列表页面
'''
page_type = '分类标签归档'
@ -259,6 +265,7 @@ class TagDetailView(ArticleListView):
return super(TagDetailView, self).get_context_data(**kwargs)
# 文章归档视图
class ArchivesView(ArticleListView):
'''
文章归档页面
@ -276,7 +283,11 @@ class ArchivesView(ArticleListView):
return cache_key
# 友情链接视图
class LinkListView(ListView):
'''
友情链接页面
'''
model = Links
template_name = 'blog/links_list.html'
@ -284,7 +295,11 @@ class LinkListView(ListView):
return Links.objects.filter(is_enable=True)
# 搜索视图
class EsSearchView(SearchView):
'''
自定义搜索视图
'''
def get_context(self):
paginator, page = self.build_page()
context = {
@ -301,12 +316,11 @@ class EsSearchView(SearchView):
return context
# 文件上传视图
@csrf_exempt
def fileupload(request):
"""
该方法需自己写调用端来上传图片该方法仅提供图床功能
:param request:
:return:
提供图床功能的文件上传接口
"""
if request.method == 'POST':
sign = request.GET.get('sign', None)
@ -341,10 +355,11 @@ def fileupload(request):
return HttpResponse("only for post")
def page_not_found_view(
request,
exception,
template_name='blog/error_page.html'):
# 自定义错误页面视图
def page_not_found_view(request, exception, template_name='blog/error_page.html'):
'''
404 页面未找到错误处理
'''
if exception:
logger.error(exception)
url = request.get_full_path()
@ -356,6 +371,9 @@ def page_not_found_view(
def server_error_view(request, template_name='blog/error_page.html'):
'''
500 服务器错误处理
'''
return render(request,
template_name,
{'message': _('Sorry, the server is busy, please click the home page to see other?'),
@ -363,10 +381,10 @@ def server_error_view(request, template_name='blog/error_page.html'):
status=500)
def permission_denied_view(
request,
exception,
template_name='blog/error_page.html'):
def permission_denied_view(request, exception, template_name='blog/error_page.html'):
'''
403 权限不足错误处理
'''
if exception:
logger.error(exception)
return render(
@ -375,6 +393,10 @@ def permission_denied_view(
'statuscode': '403'}, status=403)
# 清理缓存视图
def clean_cache_view(request):
'''
清理缓存
'''
cache.clear()
return HttpResponse('ok')

@ -3,47 +3,74 @@ from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
# 定义一个动作函数,用于批量禁用评论状态
def disable_commentstatus(modeladmin, request, queryset):
queryset.update(is_enable=False)
queryset.update(is_enable=False) # 将选中的评论的 `is_enable` 字段设置为 False
# 定义一个动作函数,用于批量启用评论状态
def enable_commentstatus(modeladmin, request, queryset):
queryset.update(is_enable=True)
queryset.update(is_enable=True) # 将选中的评论的 `is_enable` 字段设置为 True
# 为动作函数设置描述信息,显示在 Django Admin 的操作下拉菜单中
disable_commentstatus.short_description = _('Disable comments')
enable_commentstatus.short_description = _('Enable comments')
# 定义评论模型的管理类
class CommentAdmin(admin.ModelAdmin):
# 每页显示 20 条记录
list_per_page = 20
# 定义在列表页面显示的字段
list_display = (
'id',
'body',
'link_to_userinfo',
'link_to_article',
'is_enable',
'creation_time')
'id', # 评论的 ID
'body', # 评论内容
'link_to_userinfo', # 链接到用户信息
'link_to_article', # 链接到文章信息
'is_enable', # 评论是否启用
'creation_time' # 评论的创建时间
)
# 定义哪些字段可以作为链接,点击后进入详情页面
list_display_links = ('id', 'body', 'is_enable')
# 定义过滤器,允许按 `is_enable` 字段筛选
list_filter = ('is_enable',)
# 定义在表单中排除的字段
exclude = ('creation_time', 'last_modify_time')
# 定义批量操作
actions = [disable_commentstatus, enable_commentstatus]
# 定义外键字段的快速选择功能
raw_id_fields = ('author', 'article')
# 定义搜索字段,支持通过 `body` 字段搜索评论
search_fields = ('body',)
# 定义一个方法,生成链接到用户信息的 HTML
def link_to_userinfo(self, obj):
# 获取用户模型的 app_label 和 model_name
info = (obj.author._meta.app_label, obj.author._meta.model_name)
# 生成用户详情页面的链接
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
# 返回 HTML 链接,显示用户昵称或邮箱
return format_html(
u'<a href="%s">%s</a>' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
(link, obj.author.nickname if obj.author.nickname else obj.author.email)
)
# 定义一个方法,生成链接到文章信息的 HTML
def link_to_article(self, obj):
# 获取文章模型的 app_label 和 model_name
info = (obj.article._meta.app_label, obj.article._meta.model_name)
# 生成文章详情页面的链接
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
# 返回 HTML 链接,显示文章标题
return format_html(
u'<a href="%s">%s</a>' % (link, obj.article.title))
u'<a href="%s">%s</a>' % (link, obj.article.title)
)
# 设置方法的显示名称,用于 Django Admin 列表页面
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')
link_to_article.short_description = _('Article')

@ -1,17 +1,27 @@
import logging
from django.utils.translation import gettext_lazy as _
from djangoblog.utils import get_current_site
from djangoblog.utils import send_email
logger = logging.getLogger(__name__)
from django.utils.translation import gettext_lazy as _ # 用于支持多语言翻译
from djangoblog.utils import get_current_site # 获取当前站点的工具函数
from djangoblog.utils import send_email # 发送邮件的工具函数
logger = logging.getLogger(__name__) # 初始化日志记录器,用于记录错误或调试信息
def send_comment_email(comment):
"""
功能发送评论相关的通知邮件
参数
comment: 当前的评论对象包含评论内容作者信息文章信息等
"""
# 获取当前站点的域名
site = get_current_site().domain
subject = _('Thanks for your comment')
# 定义邮件主题
subject = _('Thanks for your comment') # 邮件主题,支持多语言
# 构造文章的绝对 URL
article_url = f"https://{site}{comment.article.get_absolute_url()}"
# 构造邮件内容HTML 格式),感谢用户的评论
html_content = _("""<p>Thank you very much for your comments on this site</p>
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
to review your comments,
@ -19,10 +29,17 @@ def send_comment_email(comment):
<br />
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
# 获取评论作者的邮箱
tomail = comment.author.email
# 发送邮件给评论作者
send_email([tomail], subject, html_content)
try:
# 如果当前评论是对其他评论的回复
if comment.parent_comment:
# 构造回复通知邮件内容
html_content = _("""Your comment on <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has
received a reply. <br/> %(comment_body)s
<br/>
@ -32,7 +49,12 @@ def send_comment_email(comment):
%(article_url)s
""") % {'article_url': article_url, 'article_title': comment.article.title,
'comment_body': comment.parent_comment.body}
# 获取被回复评论作者的邮箱
tomail = comment.parent_comment.author.email
# 发送邮件给被回复的评论作者
send_email([tomail], subject, html_content)
except Exception as e:
logger.error(e)
# 如果发送邮件时发生异常,记录错误日志
logger.error(e)

@ -1,63 +1,91 @@
# Create your views here.
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
from django.views.generic.edit import FormView
from accounts.models import BlogUser
from blog.models import Article
from .forms import CommentForm
from .models import Comment
# 导入必要的模块和类
from django.core.exceptions import ValidationError # 用于抛出验证错误
from django.http import HttpResponseRedirect # 用于重定向 HTTP 请求
from django.shortcuts import get_object_or_404 # 用于获取对象或返回 404 错误
from django.utils.decorators import method_decorator # 用于装饰类方法
from django.views.decorators.csrf import csrf_protect # 用于保护视图免受 CSRF 攻击
from django.views.generic.edit import FormView # 通用视图,用于处理表单
from accounts.models import BlogUser # 导入用户模型
from blog.models import Article # 导入文章模型
from .forms import CommentForm # 导入评论表单
from .models import Comment # 导入评论模型
# 定义一个基于表单的视图,用于处理评论提交
class CommentPostView(FormView):
form_class = CommentForm
template_name = 'blog/article_detail.html'
form_class = CommentForm # 指定使用的表单类
template_name = 'blog/article_detail.html' # 指定模板文件
# 使用 CSRF 保护装饰器保护 dispatch 方法
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
# 调用父类的 dispatch 方法
return super(CommentPostView, self).dispatch(*args, **kwargs)
# 处理 GET 请求
def get(self, request, *args, **kwargs):
# 获取文章 ID
article_id = self.kwargs['article_id']
# 根据文章 ID 获取文章对象,如果不存在则返回 404
article = get_object_or_404(Article, pk=article_id)
# 获取文章的绝对 URL
url = article.get_absolute_url()
# 重定向到文章详情页,并定位到评论部分
return HttpResponseRedirect(url + "#comments")
# 表单验证失败时的处理逻辑
def form_invalid(self, form):
# 获取文章 ID
article_id = self.kwargs['article_id']
# 根据文章 ID 获取文章对象
article = get_object_or_404(Article, pk=article_id)
# 返回包含表单和文章对象的响应
return self.render_to_response({
'form': form,
'article': article
})
# 表单验证成功时的处理逻辑
def form_valid(self, form):
"""提交的数据验证合法后的逻辑"""
# 获取当前登录用户
user = self.request.user
# 根据用户 ID 获取用户对象
author = BlogUser.objects.get(pk=user.pk)
# 获取文章 ID
article_id = self.kwargs['article_id']
# 根据文章 ID 获取文章对象
article = get_object_or_404(Article, pk=article_id)
# 检查文章是否允许评论
if article.comment_status == 'c' or article.status == 'c':
raise ValidationError("该文章评论已关闭.")
raise ValidationError("该文章评论已关闭.") # 抛出验证错误
# 创建评论对象,但不保存到数据库
comment = form.save(False)
comment.article = article
comment.article = article # 关联评论到文章
# 导入博客设置工具
from djangoblog.utils import get_blog_setting
settings = get_blog_setting()
settings = get_blog_setting() # 获取博客设置
# 如果评论不需要审核,则直接启用评论
if not settings.comment_need_review:
comment.is_enable = True
comment.author = author
comment.author = author # 设置评论的作者
# 如果存在父评论 ID则关联父评论
if form.cleaned_data['parent_comment_id']:
parent_comment = Comment.objects.get(
pk=form.cleaned_data['parent_comment_id'])
comment.parent_comment = parent_comment
# 保存评论到数据库
comment.save(True)
# 重定向到文章详情页,并定位到新评论的位置
return HttpResponseRedirect(
"%s#div-comment-%d" %
(article.get_absolute_url(), comment.pk))
(article.get_absolute_url(), comment.pk))

@ -0,0 +1,164 @@
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
/* Greenlet object interface */
#ifndef Py_GREENLETOBJECT_H
#define Py_GREENLETOBJECT_H
#include <Python.h>
#ifdef __cplusplus
extern "C" {
#endif
/* This is deprecated and undocumented. It does not change. */
#define GREENLET_VERSION "1.0.0"
#ifndef GREENLET_MODULE
#define implementation_ptr_t void*
#endif
typedef struct _greenlet {
PyObject_HEAD
PyObject* weakreflist;
PyObject* dict;
implementation_ptr_t pimpl;
} PyGreenlet;
#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
/* C API functions */
/* Total number of symbols that are exported */
#define PyGreenlet_API_pointers 12
#define PyGreenlet_Type_NUM 0
#define PyExc_GreenletError_NUM 1
#define PyExc_GreenletExit_NUM 2
#define PyGreenlet_New_NUM 3
#define PyGreenlet_GetCurrent_NUM 4
#define PyGreenlet_Throw_NUM 5
#define PyGreenlet_Switch_NUM 6
#define PyGreenlet_SetParent_NUM 7
#define PyGreenlet_MAIN_NUM 8
#define PyGreenlet_STARTED_NUM 9
#define PyGreenlet_ACTIVE_NUM 10
#define PyGreenlet_GET_PARENT_NUM 11
#ifndef GREENLET_MODULE
/* This section is used by modules that uses the greenlet C API */
static void** _PyGreenlet_API = NULL;
# define PyGreenlet_Type \
(*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
# define PyExc_GreenletError \
((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
# define PyExc_GreenletExit \
((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
/*
* PyGreenlet_New(PyObject *args)
*
* greenlet.greenlet(run, parent=None)
*/
# define PyGreenlet_New \
(*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
_PyGreenlet_API[PyGreenlet_New_NUM])
/*
* PyGreenlet_GetCurrent(void)
*
* greenlet.getcurrent()
*/
# define PyGreenlet_GetCurrent \
(*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
/*
* PyGreenlet_Throw(
* PyGreenlet *greenlet,
* PyObject *typ,
* PyObject *val,
* PyObject *tb)
*
* g.throw(...)
*/
# define PyGreenlet_Throw \
(*(PyObject * (*)(PyGreenlet * self, \
PyObject * typ, \
PyObject * val, \
PyObject * tb)) \
_PyGreenlet_API[PyGreenlet_Throw_NUM])
/*
* PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
*
* g.switch(*args, **kwargs)
*/
# define PyGreenlet_Switch \
(*(PyObject * \
(*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
_PyGreenlet_API[PyGreenlet_Switch_NUM])
/*
* PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
*
* g.parent = new_parent
*/
# define PyGreenlet_SetParent \
(*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
_PyGreenlet_API[PyGreenlet_SetParent_NUM])
/*
* PyGreenlet_GetParent(PyObject* greenlet)
*
* return greenlet.parent;
*
* This could return NULL even if there is no exception active.
* If it does not return NULL, you are responsible for decrementing the
* reference count.
*/
# define PyGreenlet_GetParent \
(*(PyGreenlet* (*)(PyGreenlet*)) \
_PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
/*
* deprecated, undocumented alias.
*/
# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
# define PyGreenlet_MAIN \
(*(int (*)(PyGreenlet*)) \
_PyGreenlet_API[PyGreenlet_MAIN_NUM])
# define PyGreenlet_STARTED \
(*(int (*)(PyGreenlet*)) \
_PyGreenlet_API[PyGreenlet_STARTED_NUM])
# define PyGreenlet_ACTIVE \
(*(int (*)(PyGreenlet*)) \
_PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
/* Macro that imports greenlet and initializes C API */
/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
keep the older definition to be sure older code that might have a copy of
the header still works. */
# define PyGreenlet_Import() \
{ \
_PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
}
#endif /* GREENLET_MODULE */
#ifdef __cplusplus
}
#endif
#endif /* !Py_GREENLETOBJECT_H */

@ -0,0 +1,502 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)
<# Function declarations --------------------------------------------------- #>
<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}
# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}
# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}
# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}
# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}
# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}
<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }
if ($pyvenvConfigPath) {
Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]
# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}
$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}
<# Begin Activate script --------------------------------------------------- #>
# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}
# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}
Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"
# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
Write-Verbose "Setting prompt to '$Prompt'"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
$env:VIRTUAL_ENV_PROMPT = $Prompt
}
# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}
# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
# SIG # Begin signature block
# MIIvIwYJKoZIhvcNAQcCoIIvFDCCLxACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBnL745ElCYk8vk
# dBtMuQhLeWJ3ZGfzKW4DHCYzAn+QB6CCE8MwggWQMIIDeKADAgECAhAFmxtXno4h
# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z
# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z
# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ
# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s
# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL
# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb
# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3
# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c
# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx
# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0
# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL
# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud
# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf
# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk
# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS
# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK
# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB
# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp
# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg
# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri
# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7
# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5
# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3
# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H
# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G
# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C
# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce
# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da
# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T
# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA
# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh
# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM
# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z
# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05
# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY
# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP
# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN
# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry
# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL
# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf
# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh
# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh
# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV
# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j
# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH
# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC
# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l
# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW
# eE4wggd3MIIFX6ADAgECAhAHHxQbizANJfMU6yMM0NHdMA0GCSqGSIb3DQEBCwUA
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwHhcNMjIwMTE3MDAwMDAwWhcNMjUwMTE1MjM1OTU5WjB8MQsw
# CQYDVQQGEwJVUzEPMA0GA1UECBMGT3JlZ29uMRIwEAYDVQQHEwlCZWF2ZXJ0b24x
# IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMSMwIQYDVQQDExpQ
# eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
# ADCCAgoCggIBAKgc0BTT+iKbtK6f2mr9pNMUTcAJxKdsuOiSYgDFfwhjQy89koM7
# uP+QV/gwx8MzEt3c9tLJvDccVWQ8H7mVsk/K+X+IufBLCgUi0GGAZUegEAeRlSXx
# xhYScr818ma8EvGIZdiSOhqjYc4KnfgfIS4RLtZSrDFG2tN16yS8skFa3IHyvWdb
# D9PvZ4iYNAS4pjYDRjT/9uzPZ4Pan+53xZIcDgjiTwOh8VGuppxcia6a7xCyKoOA
# GjvCyQsj5223v1/Ig7Dp9mGI+nh1E3IwmyTIIuVHyK6Lqu352diDY+iCMpk9Zanm
# SjmB+GMVs+H/gOiofjjtf6oz0ki3rb7sQ8fTnonIL9dyGTJ0ZFYKeb6BLA66d2GA
# LwxZhLe5WH4Np9HcyXHACkppsE6ynYjTOd7+jN1PRJahN1oERzTzEiV6nCO1M3U1
# HbPTGyq52IMFSBM2/07WTJSbOeXjvYR7aUxK9/ZkJiacl2iZI7IWe7JKhHohqKuc
# eQNyOzxTakLcRkzynvIrk33R9YVqtB4L6wtFxhUjvDnQg16xot2KVPdfyPAWd81w
# tZADmrUtsZ9qG79x1hBdyOl4vUtVPECuyhCxaw+faVjumapPUnwo8ygflJJ74J+B
# Yxf6UuD7m8yzsfXWkdv52DjL74TxzuFTLHPyARWCSCAbzn3ZIly+qIqDAgMBAAGj
# ggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfROQjAdBgNVHQ4E
# FgQUt/1Teh2XDuUj2WW3siYWJgkZHA8wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM
# MAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI
# QTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20v
# RGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0Ex
# LmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8v
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWdu
# aW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZI
# hvcNAQELBQADggIBABxv4AeV/5ltkELHSC63fXAFYS5tadcWTiNc2rskrNLrfH1N
# s0vgSZFoQxYBFKI159E8oQQ1SKbTEubZ/B9kmHPhprHya08+VVzxC88pOEvz68nA
# 82oEM09584aILqYmj8Pj7h/kmZNzuEL7WiwFa/U1hX+XiWfLIJQsAHBla0i7QRF2
# de8/VSF0XXFa2kBQ6aiTsiLyKPNbaNtbcucaUdn6vVUS5izWOXM95BSkFSKdE45O
# q3FForNJXjBvSCpwcP36WklaHL+aHu1upIhCTUkzTHMh8b86WmjRUqbrnvdyR2yd
# I5l1OqcMBjkpPpIV6wcc+KY/RH2xvVuuoHjlUjwq2bHiNoX+W1scCpnA8YTs2d50
# jDHUgwUo+ciwpffH0Riq132NFmrH3r67VaN3TuBxjI8SIZM58WEDkbeoriDk3hxU
# 8ZWV7b8AW6oyVBGfM06UgkfMb58h+tJPrFx8VI/WLq1dTqMfZOm5cuclMnUHs2uq
# rRNtnV8UfidPBL4ZHkTcClQbCoz0UbLhkiDvIS00Dn+BBcxw/TKqVL4Oaz3bkMSs
# M46LciTeucHY9ExRVt3zy7i149sd+F4QozPqn7FrSVHXmem3r7bjyHTxOgqxRCVa
# 18Vtx7P/8bYSBeS+WHCKcliFCecspusCDSlnRUjZwyPdP0VHxaZg2unjHY3rMYIa
# tjCCGrICAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu
# Yy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJT
# QTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAHHxQbizANJfMU6yMM0NHdMA0GCWCGSAFl
# AwQCAQUAoIHKMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC
# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBnAZ6P7YvTwq0fbF62
# o7E75R0LxsW5OtyYiFESQckLhjBeBgorBgEEAYI3AgEMMVAwTqBIgEYAQgB1AGkA
# bAB0ADoAIABSAGUAbABlAGEAcwBlAF8AdgAzAC4AMQAwAC4AMQAxAF8AMgAwADIA
# MwAwADQAMAA1AC4AMAAxoQKAADANBgkqhkiG9w0BAQEFAASCAgAGUpnYl5pjPDC8
# uJclKp0WgZwr0W3huu2nUQgdQt24qZVmblWWESswIiqJ5FC7YnGxQ6AA57xsPKgz
# GHAIoJw7ETPQjC1IonI4yvI+/8Aw+RZ7m3eDaKCk/Wbs3as7AFaCoPrjxusZGO4y
# VGY0K5zx9Pi17AepkEA+nteZlNbWRNprY1BdQep4fUVykS7+KoqmI8eiGpJe4mtD
# SlXvap7Dqz3OSBJRyb4DecJeBvBflMdCuC+mjW7wskHm8B1oCjtKgnIzETXJOe9N
# Sw98CEHVWOBDqJyMG0jOs3V5hn0li/+esIfsAEl6xDoO+9GRlQKlZHOTDYf0uJaH
# NCqLuSgpHPz0zSWPQkp1GladJxRWUHaxi7NYznMHblCDH2p8pF1ibpbKvxaxMGX8
# 0j+vAK/pzUK0HfZaY79scZn6q/kwQWjahFT32onbVH48QFTYUMBKfg1zjnQZtTnU
# Clv+Chk75xkPiyOVyd6frpK8I2jfPkXjSdIkRWGqaOkHcVrhKae8zPH+49Q+UDIX
# wjMmCuIarJzFtqh+Iu6eSlj/72q7/C2bwb0r+HkdaU3dRzxvYOqyQ6g0Cn4g+twh
# VTFKywiUiW6muz5HP7pJ9v3WUU+hpFx5WWb2MYQEO/Qh53iYGmLaT+8OvCuXM8Hm
# gmFbKlK7BtSHpVCOyiYW54YizjVvBaGCFz0wghc5BgorBgEEAYI3AwMBMYIXKTCC
# FyUGCSqGSIb3DQEHAqCCFxYwghcSAgEDMQ8wDQYJYIZIAWUDBAIBBQAwdwYLKoZI
# hvcNAQkQAQSgaARmMGQCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCBI
# 1dbHE57ZZcjKKZByi4HxJFntDaj547aEW4zgjY+zlQIQOybzqjbuRhUI00KoSULR
# UBgPMjAyMzA0MDUwMDQ1NDdaoIITBzCCBsAwggSooAMCAQICEAxNaXJLlPo8Kko9
# KQeAPVowDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp
# Z2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQw
# OTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTAeFw0yMjA5MjEwMDAwMDBaFw0zMzEx
# MjEyMzU5NTlaMEYxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEkMCIG
# A1UEAxMbRGlnaUNlcnQgVGltZXN0YW1wIDIwMjIgLSAyMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAz+ylJjrGqfJru43BDZrboegUhXQzGias0BxVHh42
# bbySVQxh9J0Jdz0Vlggva2Sk/QaDFteRkjgcMQKW+3KxlzpVrzPsYYrppijbkGNc
# vYlT4DotjIdCriak5Lt4eLl6FuFWxsC6ZFO7KhbnUEi7iGkMiMbxvuAvfTuxylON
# QIMe58tySSgeTIAehVbnhe3yYbyqOgd99qtu5Wbd4lz1L+2N1E2VhGjjgMtqedHS
# EJFGKes+JvK0jM1MuWbIu6pQOA3ljJRdGVq/9XtAbm8WqJqclUeGhXk+DF5mjBoK
# JL6cqtKctvdPbnjEKD+jHA9QBje6CNk1prUe2nhYHTno+EyREJZ+TeHdwq2lfvgt
# Gx/sK0YYoxn2Off1wU9xLokDEaJLu5i/+k/kezbvBkTkVf826uV8MefzwlLE5hZ7
# Wn6lJXPbwGqZIS1j5Vn1TS+QHye30qsU5Thmh1EIa/tTQznQZPpWz+D0CuYUbWR4
# u5j9lMNzIfMvwi4g14Gs0/EH1OG92V1LbjGUKYvmQaRllMBY5eUuKZCmt2Fk+tkg
# bBhRYLqmgQ8JJVPxvzvpqwcOagc5YhnJ1oV/E9mNec9ixezhe7nMZxMHmsF47caI
# yLBuMnnHC1mDjcbu9Sx8e47LZInxscS451NeX1XSfRkpWQNO+l3qRXMchH7XzuLU
# OncCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG
# A1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG
# SAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4E
# FgQUYore0GH8jzEU7ZcLzT0qlBTfUpwwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDov
# L2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1
# NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUH
# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDov
# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNI
# QTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAVaoqGvNG
# 83hXNzD8deNP1oUj8fz5lTmbJeb3coqYw3fUZPwV+zbCSVEseIhjVQlGOQD8adTK
# myn7oz/AyQCbEx2wmIncePLNfIXNU52vYuJhZqMUKkWHSphCK1D8G7WeCDAJ+uQt
# 1wmJefkJ5ojOfRu4aqKbwVNgCeijuJ3XrR8cuOyYQfD2DoD75P/fnRCn6wC6X0qP
# GjpStOq/CUkVNTZZmg9U0rIbf35eCa12VIp0bcrSBWcrduv/mLImlTgZiEQU5QpZ
# omvnIj5EIdI/HMCb7XxIstiSDJFPPGaUr10CU+ue4p7k0x+GAWScAMLpWnR1DT3h
# eYi/HAGXyRkjgNc2Wl+WFrFjDMZGQDvOXTXUWT5Dmhiuw8nLw/ubE19qtcfg8wXD
# Wd8nYiveQclTuf80EGf2JjKYe/5cQpSBlIKdrAqLxksVStOYkEVgM4DgI974A6T2
# RUflzrgDQkfoQTZxd639ouiXdE4u2h4djFrIHprVwvDGIqhPm73YHJpRxC+a9l+n
# J5e6li6FV8Bg53hWf2rvwpWaSxECyIKcyRoFfLpxtU56mWz06J7UWpjIn7+Nuxhc
# Q/XQKujiYu54BNu90ftbCqhwfvCXhHjjCANdRyxjqCU4lwHSPzra5eX25pvcfizM
# /xdMTQCi2NYBDriL7ubgclWJLCcZYfZ3AYwwggauMIIElqADAgECAhAHNje3JFR8
# 2Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0z
# NzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1
# NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI
# 82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9
# xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ
# 3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5Emfv
# DqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDET
# qVcplicu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHe
# IhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jo
# n7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ
# 9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/T
# Xkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJg
# o1gJASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkw
# EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+e
# yG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQD
# AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEF
# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRw
# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy
# dDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg
# hkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGw
# GC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0
# MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1D
# X+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw
# 1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY
# +/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0I
# SQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr
# 5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7y
# Rp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDop
# hrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/
# AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMO
# Hds3OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkq
# hkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB
# c3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5
# WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
# ExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJv
# b3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1K
# PDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2r
# snnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C
# 8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBf
# sXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY
# QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8
# rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaY
# dj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+
# wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw
# ++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+N
# P8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7F
# wI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUw
# AwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAU
# Reuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEB
# BG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsG
# AQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1
# cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAow
# CDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/
# Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLe
# JLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE
# 1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9Hda
# XFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbO
# byMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYID
# djCCA3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu
# Yy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYg
# VGltZVN0YW1waW5nIENBAhAMTWlyS5T6PCpKPSkHgD1aMA0GCWCGSAFlAwQCAQUA
# oIHRMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcN
# MjMwNDA1MDA0NTQ3WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBTzhyJNhjOCkjWp
# lLy9j5bp/hx8czAvBgkqhkiG9w0BCQQxIgQgUjSjrzWa1N9tY3HG2o0Php0YCn7i
# UqqdaCMru/DoqI4wNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgx/ThvjIoiSCr4iY6
# vhrE/E/meBwtZNBMgHVXoCO1tvowDQYJKoZIhvcNAQEBBQAEggIARWFWKOxm+FsN
# OV+ONMrWYC+repZLFGKHc5n3dC+cu+FoAsMy561MGvEBnittRqdypXAfKaZ3Ccj4
# 82B9mWiPNcm/LzEGj2MF2hCS/SlN+g/h9JPDOVZtXcXsnH9lalQZzJLCOdEpCdKl
# NtEYQhVw48quqNSqm55liXFPZv5atRCLq0yO7CEgGTpK6PdmEZzAavzFLtQnvDJj
# JerOZ5NW99tNaYqkJh/Q7rpB7E1UXJjFWwegaMGR4DqHqySB6RAIlNf5HaCT+3KO
# ICGKrNS3wL9WtBYlLIIEm2//Fo3m2CPfp6D3bzDw4Gjb6+BZZBX/jc++OHFLkTEp
# hB9Z1SyLC3TJa3x+ze7p84q/eYs1xqjRIoy3mkQ9gAndWCktfaOp1wAwP4oySENY
# 0Ztionj+H/iydIQNKscWZ95uj/ZTm79OW67X2hLmGOv0ukNck+FE7tHN8I4Lh6VX
# TvjYh8p2SbGHd5v60wqYgrBm5k/r9cacjaptbfl0iP4lY4jqYKnpD3gAvegh5tA4
# xCmikLbNT99M080eLf2ES/UGqF8THAfLHZXlrFFcJQ3WvwgoiRCTv2ifVlxUXwxB
# lMOfJY3zIEnrxag0ZMJciZX21rKW4ZFoU50q7Nd9+T830tfjwaJWfSNE9Sisr4id
# UvBU/gsB/5d1HPBlxQfXvxm/TMUDeT4=
# SIG # End signature block

@ -0,0 +1,69 @@
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="F:\DjangoBlog\src\myenv"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/Scripts:$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1="(myenv) ${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT="(myenv) "
export VIRTUAL_ENV_PROMPT
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi

@ -0,0 +1,34 @@
@echo off
rem This file is UTF-8 encoded, so we need to update the current code page while executing it
for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do (
set _OLD_CODEPAGE=%%a
)
if defined _OLD_CODEPAGE (
"%SystemRoot%\System32\chcp.com" 65001 > nul
)
set VIRTUAL_ENV=F:\DjangoBlog\src\myenv
if not defined PROMPT set PROMPT=$P$G
if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT%
if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%
set _OLD_VIRTUAL_PROMPT=%PROMPT%
set PROMPT=(myenv) %PROMPT%
if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%
set PYTHONHOME=
if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%
if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH%
set PATH=%VIRTUAL_ENV%\Scripts;%PATH%
set VIRTUAL_ENV_PROMPT=(myenv)
:END
if defined _OLD_CODEPAGE (
"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
set _OLD_CODEPAGE=
)

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

@ -0,0 +1,22 @@
@echo off
if defined _OLD_VIRTUAL_PROMPT (
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
)
set _OLD_VIRTUAL_PROMPT=
if defined _OLD_VIRTUAL_PYTHONHOME (
set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
set _OLD_VIRTUAL_PYTHONHOME=
)
if defined _OLD_VIRTUAL_PATH (
set "PATH=%_OLD_VIRTUAL_PATH%"
)
set _OLD_VIRTUAL_PATH=
set VIRTUAL_ENV=
set VIRTUAL_ENV_PROMPT=
:END

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,3 @@
home = G:\py310
include-system-site-packages = false
version = 3.10.11

@ -1,54 +1,68 @@
import logging
import logging # 导入日志模块,用于记录日志信息
from django.contrib import admin
from django.contrib import admin # 导入Django的admin模块用于管理后台
# Register your models here.
from django.urls import reverse
from django.utils.html import format_html
logger = logging.getLogger(__name__)
from django.urls import reverse # 导入reverse函数用于生成URL
from django.utils.html import format_html # 导入format_html函数用于生成HTML内容
logger = logging.getLogger(__name__) # 获取当前模块的日志记录器
# 定义OAuthUserAdmin类用于自定义OAuth用户模型在Django Admin中的显示和管理
class OAuthUserAdmin(admin.ModelAdmin):
# 定义搜索字段,支持通过昵称和邮箱搜索
search_fields = ('nickname', 'email')
# 每页显示的记录数
list_per_page = 20
# 定义列表页显示的字段
list_display = (
'id',
'nickname',
'link_to_usermodel',
'show_user_image',
'type',
'email',
'id', # 用户ID
'nickname', # 用户昵称
'link_to_usermodel', # 链接到用户模型
'show_user_image', # 显示用户头像
'type', # 用户类型
'email', # 用户邮箱
)
# 定义可以点击进入详情页的字段
list_display_links = ('id', 'nickname')
# 定义过滤器,支持按作者和类型过滤
list_filter = ('author', 'type',)
# 定义只读字段,初始为空
readonly_fields = []
# 动态获取只读字段
def get_readonly_fields(self, request, obj=None):
return list(self.readonly_fields) + \
[field.name for field in obj._meta.fields] + \
[field.name for field in obj._meta.many_to_many]
[field.name for field in obj._meta.fields] + [field.name for field in obj._meta.many_to_many] # 获取所有多对多字段
# 禁止添加新记录
def has_add_permission(self, request):
return False
# 定义一个方法,生成指向用户模型的链接
def link_to_usermodel(self, obj):
if obj.author:
info = (obj.author._meta.app_label, obj.author._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
if obj.author: # 如果存在作者
info = (obj.author._meta.app_label, obj.author._meta.model_name) # 获取作者模型的app_label和model_name
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) # 生成指向作者详情页的链接
return format_html(
u'<a href="%s">%s</a>' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
u'<a href="%s">%s</a>' % # 生成HTML超链接
(link, obj.author.nickname if obj.author.nickname else obj.author.email)) # 显示昵称或邮箱
# 定义一个方法,显示用户头像
def show_user_image(self, obj):
img = obj.picture
img = obj.picture # 获取用户头像URL
return format_html(
u'<img src="%s" style="width:50px;height:50px"></img>' %
u'<img src="%s" style="width:50px;height:50px"></img>' % # 生成HTML图片标签
(img))
# 设置方法的显示名称
link_to_usermodel.short_description = '用户'
show_user_image.short_description = '用户头像'
# 定义OAuthConfigAdmin类用于自定义OAuth配置模型在Django Admin中的显示和管理
class OAuthConfigAdmin(admin.ModelAdmin):
# 定义列表页显示的字段
list_display = ('type', 'appkey', 'appsecret', 'is_enable')
list_filter = ('type',)
# 定义过滤器,支持按类型过滤
list_filter = ('type',)

@ -25,7 +25,7 @@ from .oauthmanager import get_manager_by_type, OAuthAccessTokenException
logger = logging.getLogger(__name__)
# 获取重定向的 URL
def get_redirecturl(request):
nexturl = request.GET.get('next_url', None)
if not nexturl or nexturl == '/login/' or nexturl == '/login':
@ -34,12 +34,13 @@ def get_redirecturl(request):
p = urlparse(nexturl)
if p.netloc:
site = get_current_site().domain
# 验证 URL 是否属于当前站点
if not p.netloc.replace('www.', '') == site.replace('www.', ''):
logger.info('非法url:' + nexturl)
return "/"
return nexturl
# OAuth 登录视图
def oauthlogin(request):
type = request.GET.get('type', None)
if not type:
@ -48,10 +49,11 @@ def oauthlogin(request):
if not manager:
return HttpResponseRedirect('/')
nexturl = get_redirecturl(request)
# 获取授权 URL 并重定向
authorizeurl = manager.get_authorization_url(nexturl)
return HttpResponseRedirect(authorizeurl)
# OAuth 授权回调处理
def authorize(request):
type = request.GET.get('type', None)
if not type:
@ -61,6 +63,7 @@ def authorize(request):
return HttpResponseRedirect('/')
code = request.GET.get('code', None)
try:
# 使用授权码获取访问令牌
rsp = manager.get_access_token_by_code(code)
except OAuthAccessTokenException as e:
logger.warning("OAuthAccessTokenException:" + str(e))
@ -73,9 +76,11 @@ def authorize(request):
return HttpResponseRedirect(manager.get_authorization_url(nexturl))
user = manager.get_oauth_userinfo()
if user:
# 设置默认昵称
if not user.nickname or not user.nickname.strip():
user.nickname = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S')
try:
# 检查是否存在相同的 OAuth 用户
temp = OAuthUser.objects.get(type=type, openid=user.openid)
temp.picture = user.picture
temp.metadata = user.metadata
@ -83,17 +88,19 @@ def authorize(request):
user = temp
except ObjectDoesNotExist:
pass
# facebook的token过长
# 特殊处理 Facebook 的 token
if type == 'facebook':
user.token = ''
if user.email:
with transaction.atomic():
author = None
try:
# 获取关联的用户
author = get_user_model().objects.get(id=user.author_id)
except ObjectDoesNotExist:
pass
if not author:
# 创建新用户
result = get_user_model().objects.get_or_create(email=user.email)
author = result[0]
if result[1]:
@ -109,11 +116,13 @@ def authorize(request):
user.author = author
user.save()
# 发送登录信号
oauth_user_login_signal.send(
sender=authorize.__class__, id=user.id)
login(request, author)
return HttpResponseRedirect(nexturl)
else:
# 如果没有邮箱,跳转到邮箱绑定页面
user.save()
url = reverse('oauth:require_email', kwargs={
'oauthid': user.id
@ -123,10 +132,11 @@ def authorize(request):
else:
return HttpResponseRedirect(nexturl)
# 邮箱确认视图
def emailconfirm(request, id, sign):
if not sign:
return HttpResponseForbidden()
# 验证签名
if not get_sha256(settings.SECRET_KEY +
str(id) +
settings.SECRET_KEY).upper() == sign.upper():
@ -136,6 +146,7 @@ def emailconfirm(request, id, sign):
if oauthuser.author:
author = get_user_model().objects.get(pk=oauthuser.author_id)
else:
# 创建新用户
result = get_user_model().objects.get_or_create(email=oauthuser.email)
author = result[0]
if result[1]:
@ -145,11 +156,13 @@ def emailconfirm(request, id, sign):
author.save()
oauthuser.author = author
oauthuser.save()
# 发送登录信号
oauth_user_login_signal.send(
sender=emailconfirm.__class__,
id=oauthuser.id)
login(request, author)
# 发送绑定成功邮件
site = 'http://' + get_current_site().domain
content = _('''
<p>Congratulations, you have successfully bound your email address. You can use
@ -169,7 +182,7 @@ def emailconfirm(request, id, sign):
url = url + '?type=success'
return HttpResponseRedirect(url)
# 绑定邮箱的表单视图
class RequireEmailView(FormView):
form_class = RequireEmailForm
template_name = 'oauth/require_email.html'
@ -203,6 +216,7 @@ class RequireEmailView(FormView):
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
oauthuser.email = email
oauthuser.save()
# 生成签名并发送绑定邮件
sign = get_sha256(settings.SECRET_KEY +
str(oauthuser.id) + settings.SECRET_KEY)
site = get_current_site().domain
@ -232,7 +246,7 @@ class RequireEmailView(FormView):
url = url + '?type=email'
return HttpResponseRedirect(url)
# 绑定成功页面
def bindsuccess(request, oauthid):
type = request.GET.get('type', None)
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)

@ -2,31 +2,44 @@ from werobot.session import SessionStorage
from werobot.utils import json_loads, json_dumps
from djangoblog.utils import cache
# 导入所需的模块和工具包括缓存工具和JSON序列化/反序列化工具
# 定义MemcacheStorage类继承自SessionStorage用于实现基于缓存的会话存储
class MemcacheStorage(SessionStorage):
def __init__(self, prefix='ws_'):
# 初始化MemcacheStorage实例
# prefix: 缓存键的前缀,默认为'ws_'
self.prefix = prefix
self.cache = cache
self.cache = cache # 使用项目中定义的缓存工具
@property
def is_available(self):
# 检查缓存是否可用
value = "1"
self.set('checkavaliable', value=value)
return value == self.get('checkavaliable')
self.set('checkavaliable', value=value) # 设置一个测试键值对
return value == self.get('checkavaliable') # 验证缓存是否能正确存取数据
def key_name(self, s):
# 根据给定的键生成带前缀的完整键名
# s: 原始键
return '{prefix}{s}'.format(prefix=self.prefix, s=s)
def get(self, id):
id = self.key_name(id)
session_json = self.cache.get(id) or '{}'
return json_loads(session_json)
# 从缓存中获取会话数据
# id: 会话的唯一标识
id = self.key_name(id) # 生成完整键名
session_json = self.cache.get(id) or '{}' # 从缓存中获取数据若不存在则返回空JSON
return json_loads(session_json) # 将JSON字符串反序列化为Python对象
def set(self, id, value):
id = self.key_name(id)
self.cache.set(id, json_dumps(value))
# 将会话数据存储到缓存中
# id: 会话的唯一标识
# value: 要存储的会话数据
id = self.key_name(id) # 生成完整键名
self.cache.set(id, json_dumps(value)) # 将数据序列化为JSON字符串后存储到缓存
def delete(self, id):
id = self.key_name(id)
self.cache.delete(id)
# 从缓存中删除会话数据
# id: 会话的唯一标识
id = self.key_name(id) # 生成完整键名
self.cache.delete(id) # 删除缓存中的数据

@ -1,13 +1,17 @@
from django.contrib import admin
# Register your models here.
# 导入Django的admin模块用于注册和管理模型
# 定义CommandsAdmin类用于自定义Commands模型在Django Admin中的显示和行为
class CommandsAdmin(admin.ModelAdmin):
# 指定在列表页面中显示的字段
list_display = ('title', 'command', 'describe')
# 定义EmailSendLogAdmin类用于自定义EmailSendLog模型在Django Admin中的显示和行为
class EmailSendLogAdmin(admin.ModelAdmin):
# 指定在列表页面中显示的字段
list_display = ('title', 'emailto', 'send_result', 'creation_time')
# 指定只读字段防止这些字段在Admin中被修改
readonly_fields = (
'title',
'emailto',
@ -15,5 +19,6 @@ class EmailSendLogAdmin(admin.ModelAdmin):
'creation_time',
'content')
# 禁止在Admin中添加新的EmailSendLog记录
def has_add_permission(self, request):
return False

@ -13,6 +13,7 @@ from servermanager.api.blogapi import BlogApi
from servermanager.api.commonapi import ChatGPT, CommandHandler
from .MemcacheStorage import MemcacheStorage
# 初始化 WeRoBot 实例,设置 token 和会话存储
robot = WeRoBot(token=os.environ.get('DJANGO_WEROBOT_TOKEN')
or 'lylinux', enable_session=True)
memstorage = MemcacheStorage()
@ -23,11 +24,12 @@ else:
os.remove(os.path.join(settings.BASE_DIR, 'werobot_session'))
robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session')
# 初始化 BlogApi 和 CommandHandler 实例
blogapi = BlogApi()
cmd_handler = CommandHandler()
logger = logging.getLogger(__name__)
# 将文章转换为 WeRoBot 的 ArticlesReply 格式
def convert_to_article_reply(articles, message):
reply = ArticlesReply(message=message)
from blog.templatetags.blog_tags import truncatechars_content
@ -45,7 +47,7 @@ def convert_to_article_reply(articles, message):
reply.add_article(article)
return reply
# 搜索文章功能,匹配以 '?' 开头的消息
@robot.filter(re.compile(r"^\?.*"))
def search(message, session):
s = message.content
@ -58,14 +60,14 @@ def search(message, session):
else:
return '没有找到相关文章。'
# 获取文章分类目录,匹配 'category' 命令
@robot.filter(re.compile(r'^category\s*$', re.I))
def category(message, session):
categorys = blogapi.get_category_lists()
content = ','.join(map(lambda x: x.name, categorys))
return '所有文章分类目录:' + content
# 获取最近文章,匹配 'recent' 命令
@robot.filter(re.compile(r'^recent\s*$', re.I))
def recents(message, session):
articles = blogapi.get_recent_articles()
@ -75,7 +77,7 @@ def recents(message, session):
else:
return "暂时还没有文章"
# 帮助信息,匹配 'help' 命令
@robot.filter(re.compile('^help$', re.I))
def help(message, session):
return '''欢迎关注!
@ -97,23 +99,26 @@ def help(message, session):
PS:以上标点符号都不支持中文标点~~
'''
# 天气查询功能(占位),匹配 'weather:' 命令
@robot.filter(re.compile(r'^weather\:.*$', re.I))
def weather(message, session):
return "建设中..."
# 身份证信息查询功能(占位),匹配 'idcard:' 命令
@robot.filter(re.compile(r'^idcard\:.*$', re.I))
def idcard(message, session):
return "建设中..."
# 默认消息处理器
@robot.handler
def echo(message, session):
handler = MessageHandler(message, session)
return handler.handler()
# 消息处理类
class MessageHandler:
def __init__(self, message, session):
userid = message.source
@ -127,29 +132,38 @@ class MessageHandler:
userinfo = WxUserInfo()
self.userinfo = userinfo
# 判断用户是否为管理员
@property
def is_admin(self):
return self.userinfo.isAdmin
# 判断管理员密码是否已设置
@property
def is_password_set(self):
return self.userinfo.isPasswordSet
# 保存用户会话信息
def save_session(self):
info = jsonpickle.encode(self.userinfo)
self.session[self.userid] = info
# 消息处理逻辑
def handler(self):
info = self.message.content
# 管理员退出逻辑
if self.userinfo.isAdmin and info.upper() == 'EXIT':
self.userinfo = WxUserInfo()
self.save_session()
return "退出成功"
# 进入管理员模式
if info.upper() == 'ADMIN':
self.userinfo.isAdmin = True
self.save_session()
return "输入管理员密码"
# 验证管理员密码
if self.userinfo.isAdmin and not self.userinfo.isPasswordSet:
passwd = settings.WXADMIN
if settings.TESTING:
@ -166,6 +180,8 @@ class MessageHandler:
self.userinfo.Count += 1
self.save_session()
return "验证失败,请重新输入管理员密码:"
# 管理员命令处理逻辑
if self.userinfo.isAdmin and self.userinfo.isPasswordSet:
if self.userinfo.Command != '' and info.upper() == 'Y':
return cmd_handler.run(self.userinfo.Command)
@ -176,9 +192,11 @@ class MessageHandler:
self.save_session()
return "确认执行: " + info + " 命令?"
# 默认调用 ChatGPT 进行聊天
return ChatGPT.chat(info)
# 用户信息类
class WxUserInfo():
def __init__(self):
self.isAdmin = False

Loading…
Cancel
Save