Versun

对待生命,不妨大胆一点,因为我们终将失去它



笔记_怎么写日记

2024-01-15

Youtube:https://www.youtube.com/watch?v=E8vwGLMTx5I

Level 1 开始写

方法一:Homework for Life —《Soryworthy by Matthew Dicks》
每天结束后,问自己“今天发生在我身上的,最有故事价值的事情是什么”,然后以这件事为素材,讲一个5分钟的故事。
这种方法可以锻炼自己写故事的能力
方法二:模版
今天学到了什么?
今天见了什么人?
今天有什么惊喜?
方法三:晨间笔记 —《The Artist’s Way by Julia Cameron》
每天早上写,想到写什么,清空大脑,写15-20分钟

Level 2 写感受

方法一:感恩日记
写下感激的三件事情
找到你想感谢的人,然后举出3个这个人的优点,然后写下展现这些优点的事情,最后和TA分享这份感激

Level 3 做什么/怎么做

日记不仅仅是了解你的想法和感受,还可以帮助你弄清楚你该做什么
方法一:Odyssey Plan — 《Designing Your Life by Bill Burnett & Dave Evans》
如果我继续照这这条路走下去,5年后我的生活会是什么样的
如果我走一条完全不同的路,5年后我的生活会是什么样的
如果我走一条完全不同的路,同时我不用担心钱,也不用在乎别人的眼光,5年后我的生活会是什么样的
方法二:The Wheel of Life
把你的生活分成多个不同的组成部分,然后定期给它们打分/评价/幸福度/满意度
参考:职业/商业、财务、健康、家庭/朋友、浪漫、个人成长、娱乐和休闲、物质环境
方法三:12月庆典(The 12 Month Celebration)
从现在起12个月后,在生活的不同方面,我想和朋友庆祝什么?(加薪?创业?坚持健身?)
方法四:恐惧设定练习(Fear Setting Exercise)— Tim Ferriss
一般用户面临选择/害怕选择事
如果我去做了我害怕的事,最糟糕的后果是什么?
我能做些什么来防止那些坏事发生?
如果最坏的情况发生了,我能做什么来修复它?
如果我试过了甚至取得了小小的成功,那我会得到什么好处?
如果我害怕的事一件不做,半年、一年、三年后我的生活会怎么样
方法五:和未来的自己对话
一人饰两角,假装和85岁的自己对话


我的2023

2024-01-12

做了什么:

54321周刊
RSS翻译器
开通Twitter

变动:

博客从纯html -> Astro -> Obsidian Publish -> Listed.to
笔记软件从Obsidian -> Standard Notes

大事件:

第一次参与第三方项目沉浸式翻译插件
第一次和网友面基(Owen)
第一次赞助和被赞助
第一次长途自驾游
第一次摸到雪

习惯打卡(App: 小日常):

学习:329天
感觉良好:266天
运动:327天
吃点心:200天
读书:102天
冥想:66天
日记:48天
反思:39天
感觉非常棒:31天
无所事事:35天

新购买的服务

Mullvad VPN
Standard Notes
Pikapods
OpenAI API
Backblaze Computer Backup
Migadu mail

想学但还未学的:

htmlx
加密货币/区块链
fediverse

想培养的习惯:

看书
写作

总结

谈不上丰富,也谈不上虚度,还凑合


笔记_编写你的第一个Django应用

2023-12-18

以下内容是本人学习 Django 5.0官方介绍文档 的笔记

第一部分

创建项目

django-admin startproject mysite

这些目录和文件的用处是:

  • 最外层的 mysite/ 根目录只是你项目的容器, 根目录名称对 Django 没有影响,你可以将它重命名为任何你喜欢的名称。

  • manage.py: 一个让你用各种方式管理 Django 项目的命令行工具。你可以阅读 django-admin 和 manage.py 获取所有 manage.py 的细节。

  • 里面一层的 mysite/ 目录包含你的项目,它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。 (比如 mysite.urls).

  • mysite/__init__.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。如果你是 Python 初学者,阅读官方文档中的 更多关于包的知识

  • mysite/settings.py:Django 项目的配置文件。如果你想知道这个文件是如何工作的,请查看 Django 配置 了解细节。

  • mysite/urls.py:Django 项目的 URL 声明,就像你网站的“目录”。阅读 URL调度器 文档来获取更多关于 URL 的内容。

  • mysite/asgi.py:作为你的项目的运行在 ASGI 兼容的 Web 服务器上的入口。阅读 如何使用 ASGI 来部署 了解更多细节。

  • mysite/wsgi.py:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。

用于开发的简易服务器

python manage.py runserver
or
python manage.py runserver 80080
or
python manage.py runserver 0.0.0.0:8000

关于这个简易服务器的完整信息可以在 runserver 文档中找到。

创建投票应用

项目 VS 应用

项目和应用有什么区别?应用是一个专门做某件事的网络应用程序——比如博客系统,或者公共记录的数据库,或者小型的投票程序。项目则是一个网站使用的配置和应用的集合。项目可以包含很多个应用。应用可以被很多个项目使用。

python manage.py startapp polls

编写第一个视图

# polls/view.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

URL映射

# polls/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
]

项目的URL映射

# mysite/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("polls/", include("polls.urls")),
    path("admin/", admin.site.urls),
]

函数 path() 具有四个参数,两个必须参数:routeview,两个可选参数:kwargsname

参数:route

route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。

这些准则不会匹配 GET 和 POST 参数或域名。例如,URLconf 在处理请求 https://www.example.com/myapp/ 时,它会尝试匹配 myapp/ 。处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/

参数:view

当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。稍后,我们会给出一个例子。

参数:kwargs

任意个关键字参数可以作为一个字典传递给目标视图函数。本教程中不会使用这一特性。

参数:name

为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。

第二部分

数据库配置

mysite/settings.py是项目配置文件,默认使用SQLite作为默认数据库,如果你想使用其他数据库,你需要安装合适的 database bindings ,然后改变设置文件中 DATABASES 'default' 项目中的一些键值:

  • ENGINE — 可选值有 'django.db.backends.sqlite3''django.db.backends.postgresql''django.db.backends.mysql',或 'django.db.backends.oracle'。其它 可用后端

  • NAME — 数据库的名称。如果你使用 SQLite,数据库将是你电脑上的一个文件,在这种情况下,NAME 应该是此文件完整的绝对路径,包括文件名。默认值 BASE_DIR / 'db.sqlite3' 将把数据库文件储存在项目的根目录。

如果你不使用 SQLite,则必须添加一些额外设置,比如 USERPASSWORDHOST 等等。想了解更多数据库设置方面的内容,请看文档:DATABASES

除了SQLite外,其它数据库需在使用前手动创建数据库

执行命令python manage.py migrate

migrate 命令只会为在 INSTALLED_APPS 里声明了的应用进行数据库迁移。

创建模型

# polls/models.py
from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")
    def __str__(self):
        return self.question_text
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    def __str__(self):
        return self.choice_text​

注意在最后,我们使用 ForeignKey 定义了一个关系。这将告诉 Django,每个 Choice 对象都关联到一个 Question 对象。Django 支持所有常用的数据库关系:多对一、多对多和一对一

激活模型

# mysite/settings.py
INSTALLED_APPS = [
        # ...
    "polls.apps.PollsConfig",
    ]

运行命令 python manage.py makemigrations polls

通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移

查看具体的迁移SQL语句 python manage.py sqlmigrate polls 0001

检查项目中的问题 python manage.py check

再次运行migrate完成修改 python manage.py migrate

改变模型需要3个步骤

编辑 models.py 文件,改变模型。

运行 python manage.py makemigrations 为模型的改变生成迁移文件。

运行 python manage.py migrate 来应用数据库迁移。

数据库API

from polls.models import Choice, Question
from django.utils import timezone
current_year = timezone.now().year

Question.objects.all()
#<QuerySet [<Question: What's up?>]>

q = Question(question_text="What's new?", pub_date=timezone.now())
# <QuerySet [<Question: What's up?>]>

q.save()
q.id 
# 1

q.question_text​ 
# "What's new?"

q.pub_date 
# datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=datetime.timezone.utc)

q.question_text = "What's up?"
q.save()

Question.objects.get(pk=1) # shortcut for primary-key exact lookups.
# <Question: What's up?>

q.was_published_recently()
# True

q.choice_set.all() # 查找关联到q这个Question的所有choice集
# <QuerySet []>

q.choice_set.create(choice_text="Not much", votes=0) # 创建一个关联到q到choice类数据
# <Choice: Not much>

q.choice_set.count()
# 1

Choice.objects.filter(question__pub_date__year=current_year)
# <QuerySet [<Choice: Not much>]>

c = q.choice_set.filter(choice_text__startswith="Not much")
c.delete()

Django管理界面

管理界面不是为了网站的访问者,而是为管理者准备的

创建管理员账户

python manage.py createsuperuser

访问管理后台

如果你设置了 LANGUAGE_CODE,登录界面将显示你设置的语言(如果 Django 有相应的翻译)

向管理界面注册Question模型类

# polls/admin.py
from django.contrib import admin
from .models import Question

admin.site.register(Question)

创建模板

ref: 模板指南

默认的设置文件设置了 DjangoTemplates 后端,并将 APP_DIRS 设置成了 True。这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS 文件夹中寻找 "templates" 子目录。这就是为什么尽管我们没有像在第二部分中那样修改 DIRS 设置,Django 也能正确找到 polls 的模板位置的原因。

虽然我们现在可以将模板文件直接放在 polls/templates 文件夹中(而不是再建立一个 polls 子文件夹),但是这样做不太好。Django 将会选择第一个匹配的模板文件,如果你有一个模板文件正好和另一个应用中的某个模板文件重名,Django 没有办法 区分 它们。我们需要帮助 Django 选择正确的模板,最好的方法就是把他们放入各自的 命名空间 中,也就是把这些模板放入一个和 自身 应用重名的子文件夹里。

创建文件: polls/templates/polls/index.html

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <!--<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>-->
        <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

为了让教程看起来不那么长,所有的模板文件都只写出了核心代码。在你自己创建的项目中,你应该使用 完整的 HTML 文档 。

第三部分

视图

每一个视图表现为一个 Python 函数

Django 将会根据用户请求的 URL 来选择使用哪个视图(更准确的说,是根据 URL 中域名之后的部分)。

URL 一般形式:/newsarchive///

Django 使用 URLconfs 将 URL 模式映射到视图。

Django 只要求视图返回的是一个 HttpResponse ,或者抛出一个异常。

# polls/view.py
from django.shortcuts import render
from django.http import HttpResponse
from django.http import Http404
from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    template = loader.get_template("polls/index.html")
    context = { # 传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象
        "latest_question_list": latest_question_list,
    }
    #return HttpResponse(template.render(context, request)) #等效于render
    return render(request, "polls/index.html", context) # return an HttpResponse object

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
        response = "You're looking at the results of question %s."
        return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

把这些新视图添加进 polls.urls 模块里

# polls/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # ex: /polls/
    path("", views.index, name="index"),
    # ex: /polls/5/
    path("<int:question_id>/", views.detail, name="detail"),
    # ex: /polls/5/results/
    path("<int:question_id>/results/", views.results, name="results"),
    # ex: /polls/5/vote/
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

抛出404错误

更新polls/views.py

from django.http import Http404
#...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, "polls/detail.html", {"question": question})
#...

使用快捷函数get()

# polls/views.py
from django.shortcuts import get_object_or_404, render
from .models import Question

# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/detail.html", {"question": question})

也有 get_list_or_404()函数, 如果列表为空的话会抛出 Http404 异常。

添加detail.html

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

在示例 {{ question.question_text }} 中,首先 Django 尝试对 question 对象使用字典查找(也就是使用 obj.get(str) 操作),如果失败了就尝试属性查找(也就是 obj.str 操作),结果是成功了。如果这一操作也失败的话,将会尝试列表查找(也就是 obj[int] 操作)

为URL名称添加命名空间

举个例子,polls 应用有 detail 视图,可能另一个博客应用也有同名的视图。Django 如何知道 {% url %} 标签到底对应哪一个应用的 URL 呢?

答案是:在根 URLconf 中添加命名空间。在 polls/urls.py 文件中稍作修改,加上 app_name 设置命名空间:

# polls/urls.py
from django.urls import path
from . import views

app_name = "polls"

urlpatterns = [
    path("", views.index, name="index"),
    path("<int:question_id>/", views.detail, name="detail"),
    path("<int:question_id>/results/", views.results, name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

现在,编辑 polls/templates/polls/index.html 文件,从:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

修改为指向具有命名空间的详细视图:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

第四部分

表单

 <!-- polls/templates/polls/detail.html -->

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}
    <p><strong>{{ error_message }}</strong></p>
    {% endif %}

    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>

简要说明:

  • 上面的模板在 Question 的每个 Choice 前添加一个单选按钮。 每个单选按钮的 value 属性是对应的各个 Choice 的 ID。每个单选按钮的 name 是 "choice" 。这意味着,当有人选择一个单选按钮并提交表单提交时,它将发送一个 POST 数据 choice=# ,其中# 为选择的 Choice 的 ID。这是 HTML 表单的基本概念。

  • 我们将表单的 action 设置为 {% url 'polls:vote' question.id %},并设置 method="post"。使用 method="post" (而不是 method="get" )是非常重要的,因为提交这个表单的行为将改变服务器端的数据。当你创建一个改变服务器端数据的表单时,使用 method="post"。这不是 Django 的特定技巧;这是优秀的网站开发技巧。

  • forloop.counter 指示 for 标签已经循环多少次。

  • 由于我们创建一个 POST 表单(它具有修改数据的作用),所以我们需要小心跨站点请求伪造。 谢天谢地,你不必太过担心,因为 Django 自带了一个非常有用的防御系统。 简而言之,所有针对内部 URL 的 POST 表单都应该使用 {% csrf_token %} 模板标签。

更新polls/views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Choice, Question
# ...
def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/results.html", {"question": question})

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST["choice"])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(
            request,
            "polls/detail.html",
            {
                "question": question,
                "error_message": "You didn't select a choice.",
            },
        )
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
#..
  • request.POST 是一个类字典对象,让你可以通过关键字的名字获取提交的数据。 这个例子中, request.POST['choice'] 以字符串形式返回选择的 Choice 的 ID。 request.POST 的值永远是字符串。注意,Django 还以同样的方式提供 request.GET 用于访问 GET 数据 —— 但我们在代码中显式地使用 request.POST ,以保证数据只能通过 POST 调用改动。

  • 如果在 request.POST['choice'] 数据中没有提供 choice , POST 将引发一个 KeyError 。上面的代码检查 KeyError ,如果没有给出 choice 将重新显示 Question 表单和一个错误信息。

  • 在增加 Choice 的得票数之后,代码返回一个 HttpResponseRedirect 而不是常用的 HttpResponse 、 HttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL(请继续看下去,我们将会解释如何构造这个例子中的 URL)。正如上面的 Python 注释指出的,在成功处理 POST 数据后,你应该总是返回一个 HttpResponseRedirect。这不是 Django 的特殊要求,这是那些优秀网站在开发实践中形成的共识。

  • 在这个例子中,我们在 HttpResponseRedirect 的构造函数中使用 reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。 在本例中,使用在 教程第 3 部分 中设定的 URLconf, reverse() 调用将返回一个这样的字符串:"/polls/3/results/"

我们的 vote() 视图代码有一个小问题。代码首先从数据库中获取了 selected_choice 对象,接着计算 vote 的新值,最后把值存回数据库。如果网站有两个方可同时投票在 同一时间 ,可能会导致问题。同样的值,42,会被 votes 返回。然后,对于两个用户,新值43计算完毕,并被保存,但是期望值是44。

这个问题被称为 竞争条件 。如果你对此有兴趣,你可以阅读 使用 F() 避免竞争条件 来学习如何解决这个问题。

使用通用视图

通用视图对常见模式进行了抽象,使您甚至不需要编写 Python 代码就能编写应用程序。例如, ListView 和 DetailView 通用视图分别抽象了 "显示对象列表 "和 "显示特定类型对象的详细页面 "的概念

更多关于通用视图的详细信息,请查看 通用视图的文档

修改polls/urls.py

from django.urls import path
from . import views

app_name = "polls"
urlpatterns = [
    path("", views.IndexView.as_view(), name="index"),
    path("<int:pk>/", views.DetailView.as_view(), name="detail"),
    path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

请注意,第二和第三个模式的路径字符串中匹配模式的名称已从  变为  。这是必要的,因为我们将使用 DetailView 通用视图来替换 detail() 和 results() 视图,它希望从 URL 获取的主键值称为 "pk"

修改polls/views.py删除旧的 indexdetail, 和 results 视图,并用 Django 的通用视图代替

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Choice, Question

class IndexView(generic.ListView):
    template_name = "polls/index.html"
    # 如果不指定template_name,则默认将会使用polls/question_list.html
    context_object_name = "latest_question_list"
    # 如果不指定context_object_name, 则默认将会使用question_list

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by("-pub_date")[:5]

class DetailView(generic.DetailView):
    model = Question
    template_name = "polls/detail.html"
    # 如果不指定template_name,则默认将会使用polls/question_detail.html

    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Question.objects.filter(pub_date__lte=timezone.now())

class ResultsView(generic.DetailView):
    model = Question
    template_name = "polls/results.html"

def vote(request, question_id):
    ...  # same as above, no changes needed.

每个通用视图都需要知道它将对什么模型采取行动。这可以通过模型属性(在本例中,DetailView 和 ResultsView 的model = Question)或定义 get_queryset() 方法(如 IndexView 所示)来提供。

第五部分

测试

ref:Django中的测试

Django 应用的测试应该写在应用的 tests.py 文件里,测试系统会自动的在所有文件里寻找并执行以 test 开头的测试函数。

模型测试

# polls/tests.py
import datetime

from django.test import TestCase
from django.utils import timezone

from .models import Question

class QuestionModelTests(TestCase):
    def test_was_published_recently_with_future_question(self):
        """
        was_published_recently() returns False for questions whose pub_date is in the future.
        """
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)

运行测试

python manage.py test polls

视图测试

Django 提供了一个供测试使用的 Client 来模拟用户和视图层代码的交互。我们能在 tests.py 甚至是 shell 中使用它。

# polls/tests.py

from django.urls import reverse
#...
def create_question(question_text, days):
    """
    Create a question with the given `question_text` and published the
    given number of `days` offset to now (negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)

class QuestionIndexViewTests(TestCase):
    def test_no_questions(self):
        """
        If no questions exist, an appropriate message is displayed.
        """
        response = self.client.get(reverse("polls:index"))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "No polls are available.")
        self.assertQuerySetEqual(response.context["latest_question_list"], [])

    def test_two_past_questions(self):
        """
        The questions index page may display multiple questions.
        """
        question1 = create_question(question_text="Past question 1.", days=-30)
        question2 = create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse("polls:index"))
        self.assertQuerySetEqual(
            response.context["latest_question_list"],
            [question2, question1],
        )

class QuestionDetailViewTests(TestCase):
    def test_future_question(self):
        """
        The detail view of a question with a pub_date in the future
        returns a 404 not found.
        """
        future_question = create_question(question_text="Future question.", days=5)
        url = reverse("polls:detail", args=(future_question.id,))
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_past_question(self):
        """
        The detail view of a question with a pub_date in the past
        displays the question's text.
        """
        past_question = create_question(question_text="Past Question.", days=-5)
        url = reverse("polls:detail", args=(past_question.id,))
        response = self.client.get(url)
        self.assertContains(response, past_question.question_text)

第六部分

静态文件

Django 的 STATICFILES_FINDERS 设置包含了一系列的查找器,它们知道去哪里找到 static 文件。AppDirectoriesFinder 是默认查找器中的一个,它会在每个 INSTALLED_APPS 中指定的应用的子文件中寻找名称为 static 的特定文件夹

建议在每个应用目录下创建static/appname文件夹来存放该应该的静态文件。

比如:polls/static/polls/style.css

你可以在 Django 中以 polls/style.css 的形式引用此文件,类似你引用模板路径的方式

虽然可以直接放到polls/static文件夹下,Django 只会使用第一个找到的静态文件。如果你在其它应用中有一个相同名字的静态文件,Django 将无法区分它们。

我们需要指引 Django 选择正确的静态文件,而最好的方式就是把它们放入各自的 命名空间 。也就是把这些静态文件放入 另一个 与应用名相同的目录中,既**polls/static/polls/**

导入静态文件

<!-- polls/templates/polls/index.html -->
{% load static %}

<link rel="stylesheet" href="{% static 'polls/style.css' %}">

{% static %} 模板标签会生成静态文件的绝对路径。

添加一个背景图

# polls/static/polls/style.css
body {
    background: white url("images/background.png") no-repeat;
}

需在**polls/static/polls/images/**文件夹下存放background.png,url使用相对路径。

更多关于设置和框架的资料,参考 静态文件解惑 和 静态文件指南部署静态文件 介绍了如何在真实服务器上使用静态文件

第七部分

自定义后台表单

# polls/admin.py
from django.contrib import admin
from .models import Question

class QuestionAdmin(admin.ModelAdmin):
    fields = ["pub_date", "question_text"]

admin.site.register(Question, QuestionAdmin)

_-django-3b063e6e.png 8.85 KB

使用字段集

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["question_text"]}),
        ("Date information", {"fields": ["pub_date"]}),
    ]

_-django-041b2b86.png 9.99 KB

关联对象

class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3 # 提供3个choice的字段

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["question_text"]}),
        ("Date information", {"fields": ["pub_date"], "classes": ["collapse"]}),
    ]
    inlines = [ChoiceInline

_-django-1c56a1ab.png 33.7 KB

使用表格式

class ChoiceInline(admin.TabularInline):
    ...

_-django-2552e6b9.png 12.3 KB

更改显示列表

# polls/admin.py
class QuestionAdmin(admin.ModelAdmin):
    # ...
    list_display = ["question_text", "pub_date","was_published_recently"]
    list_filter = ["pub_date"]
    search_fields = ["question_text"] #后台使用 LIKE 来查询数据

# polls/models.py
class Question(models.Model):
    # ...
    [@admin](https://micro.blog/admin)(
        boolean=True,
        ordering="pub_date",
        description="Published recently?",
    )
    def was_published_recently(self):
    # ...

_-django-8d8b3eeb.png 25.4 KB

其它修改:变更页分页搜索框过滤器日期层次结构, 和 列标题排序

自定义后台界面和风格

新建项目级模板mysite/templates,并添加到设置中

# mysite/settings.py
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        #...    
     }
 ]

DIRS 是一个包含多个系统目录的文件列表,用于在载入 Django 模板时使用,是一个待搜索路径。

新建文件:mysite/templates/admin/base_site.html, 可粘贴默认的模板再进行修改。

修改站点名

{% block branding %}
<div id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a><div>
{% endblock %}

在一个实际工程中,你可能更期望使用 django.contrib.admin.AdminSite.site_header 来进行简单的定制.

自定义应用管理模板

我们的投票应用不是非常复杂,所以无需自定义后台模板。不过,如果它变的更加复杂,需要修改 Django 的标准后台模板功能时,修改 应用 的模板会比 工程 的更加明智。这样,在其它工程包含这个投票应用时,可以确保它总是能找到需要的自定义模板文件。

更多关于 Django 如何查找模板的文档,参见 加载模板文档

自定义后台主页

默认情况下,它展示了所有配置在 INSTALLED_APPS 中,已通过后台应用注册,按字母排序的应用。

需要自定义的模板是 admin/index.html,修改app_list 的模板变量,可以使用硬编码链接(链接至特定对象的管理页)替代使用这个变量

第八部分

安装Django调式工具栏

这是一个常用的第三方软件包Django Debug Toolbar, 由Jazzband开发

pip install django-debug-toolbar

具体的安装方法查看官方文档

通常第三方软件包安装后,需要添加到INSTALLED_APPS中,可能还需要添加到URLconf (urls.py) 中。

还有更多的第三方软件包,可以在这里搜索

下一步看什么

可以开始你的项目了,下面是相关文档供查找

References

设计理念

创建可复用的应用教程

编写你的第一个 Django 补丁

Djiano List


Uvicorn和Guvicorn的使用场景

2023-12-13

Uvicorn 是一个异步服务器网关接口 (ASGI) 实现,它能够通过异步IO在单个进程中并发地处理多个请求。
这意味着,尽管 Uvicorn 是单线程的,但由于 Python 的异步特性,它还是能够同时处理多个请求。
这与传统的同步服务器不同,后者通常会为每个请求分配一个线程或进程。

Uvicorn 非常适合 IO 密集型的应用,如大量的网络请求和数据库操作,因为它们可以在等待 IO 操作完成时处理其他请求。
然而,对于 CPU 密集型任务,异步IO并不能提高性能,因为 Python 解释器的全局解释器锁 (GIL) 限制了在任何给定时间只有一个线程执行 Python 字节码。

因此,如果需要处理多个 CPU 密集型任务,需要使用多个 Uvicorn 工作进程来充分利用多核心 CPU。在这种情况下,可以使用像 Gunicorn 这样的工具来管理多个 Uvicorn 工作进程,每个进程都有自己的事件循环和内存空间。

在大多数情况下,Uvicorn 足以处理中等流量的应用程序,并且可以通过添加更多的工作进程来水平扩展以处理更高的负载。


在云开发机上运行uvicorn的问题

2023-12-11

今天在使用uvicorn运行fastapi的app时,遇到了几个问题:

无法在code server上正常访问

由于我的开发环境在云上,用的code server,并非是本地,因此默认命令uvicorn main:app --reload 是无法正常访问的,因为code server代理的网址是https://example.com/proxy/8000

因此需要更改root-path才可以正常访问:

uvicorn main:app --root-path https://example.com/proxy/8000 --reload

对于python的自定义包无法正常载入解析

在我的main.py中,导入自定义包:from myapp.utils.database import Database
但在myapp文件夹下执行uvicorn main:app时,会出现无法找到myapp模块的错误,因此需要返回上一级文件夹,然后执行uvicorn myapp.main:app,这样就可以了

reload监控文件夹设置

由于uvicorn在启用reload时,默认是监控当前文件夹的变动,但如果使用了uvicorn myapp.main:app,则需要指定监控的文件:

uvicorn myapp.main:app --reload --reload-dir /path/to/myapp/

综上,全部命令为:

uvicorn myapp.main:app --reload --root-path https://example.com/proxy/8000 --reload-dir /path/to/myapp/


博客搬家到Listed-to

2023-12-10

这次搬家主要是因为我的笔记应用从Obsidian(下称ob)搬到了Standard Notes(下称sn)。

我用ob也有一年多了,一直是开了sync和publish服务,但ob有很多操作一直习惯不来,比如左栏视图切换,总要想一下才能反应过来,还有就是鸡肋的搜索和没有web端,但让我下定决心搬家的原因是插件,正所谓成也插件败也插件,原先想自己做一个插件,深入了解后发现,插件的权限非常大,读取任意笔记不说,甚至可以在没有通知的情况下删除笔记……这顿时让我脊背发凉。

所以趁着黑五sn全场5折,一年只要49美金,还有免费的listed博客发布平台,比ob的sync + publish = 192美金要划算很多,而且sn不仅端到端加密,还本地加密,还有自动邮件和本地备份,操作和印象笔记也差不多,之前因为价格太贵一直没入手,所以这次就抓紧上车了。

下面简单介绍下Listed的设置,以备不时之需

关于博客数量

可以开无数个Listed,不过默认的listed.to域名国内被墙,需要绑定域名才可以在国内访问

自定义日期或者设置唯一的URL(doc):

---
date: 2017-11-20 17:08:05
canonical: [mysite.com/blog/1/po...](https://mysite.com/blog/1/post-im-importing)
---
Your story...

可用元数据字段列表:

created_at 博文创建时间

canonical 该帖子的规范 URL,供搜索引擎使用。

custom_path 覆盖帖子的默认路径。如果是从其他博客迁移过来,这很有用。(例如: my-blog-post )

desc 本帖的自定义元描述,供搜索引擎使用。

hidden true/false. 是否应从作者简介中隐藏文章(但仍可通过 URL 访问)

image_url 社交媒体网站在链接预览卡中使用的图片。

metatype [css, html, json]. 用于创建自定义主题。

page true/false. 用于在作者标题中创建专用链接。

page_link 如果 page 为 true,且设置了该值,页面将作为外部 URL 打开。

page_sort 一个数字。数字越小,页面链接出现在作者页眉的时间就越早。

关于自定义CSS

先根据官方文档创建css文件

如果想做成twitter类型,不显示正文,只显示标题:

---
metatype: css
---
.author-post .post-body {
  display: none;
}

我博客在用的css:gist.github.com/versun/38…

关于Newsletter

默认开通newsletter,所有邮件都是从[email protected]发出的,而且为了隐私安全,博主是无法看到订阅人邮箱的,只能知道人数。
所以以后如果转平台,是无法批量导出订阅人的,只能发邮件让订阅者重新在新平台订阅。

关于图片

虽然Standard Notes笔记里可以添加图片,但发布到Listed后并不会显示,因此需要自行使用图床,稍微麻烦了些


解决macOS下OneDrive的同步问题

2023-11-21

我的OneDrive下有4.7T的资料,需要全部同步下载到macOS下的外置硬盘,期间遇到了无数的问题,同步了数次才成功,因此在这里记录下主要的问题和相关的解决方案。

安装Onedrive

一定要去官网下载OneDrive,不要在Appstore里安装。

同步

初次开启OneDrive或者推出OneDrive后,如果文件数量超多,则会一直卡在“正在处理”的状态,这时不要退出,等待处理完成后就会开始下载。
建议一次一个大文件夹,不要一下子全部同步,不方便后续的核对

下载的文件

下载的文件并不在Finder左边的OneDrive文件夹里,该文件夹里都是链接文件,并非真实的文件。
真实文件都在隐藏文件夹 “.ODContainer-OneDrive” 下

比对文件大小

OneDrive官网不会显示文件夹的大小,需要进入OneDrive软件的设置里,点击“管理存储空间”可以查看
mac的对于文件大小的计算方式和微软不同,所以下载的文件大小会大于网页上显示的大小 需要在隐藏文件夹 “.ODContainer-OneDrive” 下,才可以查看文件大小 对于超大文件夹(大于2T),mac下右键文件夹详情里的文件大小有时不准确,需要进入文件夹比对子文件的大小

无法同步

一般情况下,重启软件或电脑是可以解决的
但如果软件有红色提示 存在同步问题的文件,则需要在网页上下载有问题的文件后,在网页端删除该文件。
这样软件就不会提示了,就会继续同步


macOS的Python环境配置

2023-11-18

警告:千万不要使用macOS默认安装的python。。。。
最佳配置是使用pyenv,因为它可以控制shell路径,可以配置全局默认版本

安装pyenv

Github仓库
官方安装指南
这边只建议使用Homebrew来安装,可以省很多麻烦
首先需要安装依赖 brew install openssl readline sqlite3 xz zlib tcl-tk
然后再pyenvbrew install pyenv

安装Python

可先运行pyenv install --list查看所有可安装的版本
pyenv install 3.12.0

设置全局默认

`pyenv global 3.12.0

设置默认环境

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init -)"' >> ~/.zshrc

重启终端就可以自动生效了

使用PDM来管理虚拟环境

Github仓库 | 官方文档
安装brew install pdm
初始化一个新的 PDM 项目: pdm init
(可选) 选择python版本号 pdm use 3.11
安装包:pdm add django
添加依赖: pdm add requests django


树莓派4安装vscode并开启tunnel

2023-08-28

如果你的树莓派可以外网访问,或者只想在内网使用vscode server,则建议直接使用Remove SSH会好些。
但如果无法外网访问,除了设置DDNS外,还可以使用tunnel,还可以网页访问vscode,很方便
参考:https://code.visualstudio.com/docs/remote/tunnels

如果你的树莓派是Raspberry Pi OS,则直接运行下面的代码安装即可:

sudo apt update
sudo apt install code

如果是debian或者其它第三方的系统,则运行下面的代码安装:
ref:https://code.visualstudio.com/docs/setup/linux

sudo apt-get install wget gpg
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
sudo install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list'
rm -f packages.microsoft.gpg
sudo apt install apt-transport-https
sudo apt update
sudo apt install code # or code-insiders

接着,下载CLI工具:https://code.visualstudio.com/download
选择Linux下的Arm64版本的CLI
解压后放到/usr/local/bin下

开启断开连接后服务保持功能

code tunnel service install
sudo loginctl enable-linger $USER

查看日志的命令: code tunnel service log
关闭服务保持功能: code tunnel service uninstall

开启Tunnel

code tunnel
需确保树莓派的网络能正常访问Github
后续按照提示完成认证即可

客户端配置

客户端的vscode上,安装Remote – Tunnel插件后,点击左下方的绿色按钮,选择Connect to Tunnel,通过Github认证后,你的树莓派名称就会显示,点击即可连接使用。
Web版:https://vscode.dev/tunnel/你设置的名称


NAS惊险记

2023-08-21

这周我的威联通NAS因为一次意外断电,发生了硬盘坏块,数据丢失的情况,好在之前做好了备份策略,数据无损恢复,只是浪费了些时间。
这次事件发生了很多之前完全想不到的情况 ps:人类果然无法逃脱熵增定律
在此分享下事件过程:

我的备份策略:
首先我不相信任何的软raid方案,因此我的NAS设置为raid 0,寻求最佳性能
所有文件自动备份在NAS上,并实时同步到OneDrive
个人文档和重要文件也会同步到iCloud上
NAS连接外置硬盘,定时备份重要文件
目的: 通过不同的云服务商来确保数据安全,同时确保重要文件能最快速度的恢复并能临时使用

在发生意外断电后,在检查UPS电量和电源连接情况后,重启NAS,系统提示发生意外断电,需检查磁盘,运行了一天,果然有坏块,数据丢失。
由于日常使用的文件,均在iCloud上,所以不受影响

NAS重新绑定OneDrive时,再次出现问题,由于太过信任微软的Authenticator应用,使用的e5子账户可能因为长期没有登录,应用死活收不到验证码
好在电脑上的onedrive会话还在有效期,赶紧连接nas并全部下载下来,然后新建e5账户,并开启双因子和短信验证。
NAS绑定新的OneDrive账户,开始同步

至此,数据已全部找回,并在事件发生期间没有影响到日常的使用。
教训:
1.做好nas的物理隔离
2.所有账户的登录验证方式至少激活2种以上
3.对于非重要的冷数据,有必要再找个云服务商,不能只靠onedrive,目前在考虑BorgBase服务

小插曲:在意外发生时,我还在上班,由于正好要查看一个冷数据,之前都是VPN连接到内网的NAS上查看,但这次只能使用onedrive,好在app会话有效期还在。
但在我下载后准备使用Cryptomator解密vault时,才发现这个app竟然开始收费了。。。。好在有30天的试用期,看来要重新考虑加密方案了。

2023–12–01 更新

由于远程访问文件的需求较低,因此把NAS卖了,换了硬盘柜,并使用BackBlaze的Computer Backup备份,热文件依旧使用iCloud备份