Django 1.8 官方教程翻译(第一部分)

Django 1.8 官方教程翻译(第一部分)

tags: Django 翻译


本文是我学习Django时对官方教程Writing your first Django app部分进行的翻译.由于本人能力有限,若有错漏及翻译不当之处请指出.
Django安装部分可参考官方教程或我博客中的相关内容.


Writing your first Django app, part 1

(原文链接)

教程说明

通过这个教程,我们将引导你完成一个简单投票应用的制作.
这个应用由两个部分组成:

  • 一个公共页面,可以让人们查看投票状况并进行投票.
  • 一个管理页面,让你能够增加,修改及删除投票.

我们假设你已经安装好了Django.你可以使用下面这条命令来确认Django是否已经安装,以及当前安装的Django的版本:

$ python -c "import django; print(django.get_version())"

如果安装了Django,你会看到对应的版本号,否则将得到一个"No module named django"的错误.

本教程为Django 1.8Python 3.2及更高版本而写,若你的Django版本并不符合,你可以通过当前页面(译注:当然是指原文所在的网页)右下角的版本切换器来切换到你的Django版本的教程,或者你也可以将Django升级到最新版本.假如你仍在使用Python 2.7,你需要按照注释中的描述对教材中的样例代码进行细微的调整.

你可以通过查看How to install Django来获得关于移除旧版本及安装新版本的建议.

获得帮助
若你在本教程中遇到了问题,请发送一条消息到django-users或访问#django on irc.freenode.net和其他Django用户交流,应当有人能够帮助你.

创建一个项目(Creating a project)

如果你是第一次使用Django,那么你需要对项目的初始化有所了解.Diango会自动生成一些代码来配置一个”项目”(Project,即一个由Django实例的设置组成的集合,包括数据库设置,Django的具体选项以及应用的具体设置).

在命令行下,cd到你想要存放代码的目录,然后运行下面的命令:

$ django-admin startproject mysite

这条命令将在当前目录下创建一个mysite目录.若该命令不能正常运行,请参考Problems running django-admin.

Note
你需要避免使用Python内建名称或Django组件名称作为项目名.这就是说你不能使用类似django(和Django本身冲突)或者test(和Python内建的包冲突)的名称来创建项目.

应当把代码放在那里?
如果你有使用老式PHP的经历,你可能会习惯于将代码放在Web服务的根目录(比如说/var/www).但是在Django中,你不应该这样做.将任何Python代码放在你的Web服务器的根目录都不是一个好主意,因为如果这样做你的代码将有可能被其他人通过Web看到.
基于安全性考虑,更合适的做法是将你的代码放到根目录以外的地方,比如/home/mycode.

让我们来看一下startproject为我们生成了什么:

mysite
├── manage.py
└── mysite
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

这些文件分别是:

  • 外层的mysize/根目录只是一个包含你的项目文件的容器.它的名字对Django来说并没有影响,因此你可以把它改为任何你喜欢的名字.
  • manage.py:一个让你和这个Django项目以各种方式交互的命令行工具.你可以在django-admin and manage.py页面查看所有关于manage.py的细节.
  • 内层的mysize/目录是你的项目中实际用到的Python包.你需要使用这个名字来导入其中的任何东西(比如mysite.urls).
  • mysite/_init_.py:一个空文件,用来告诉Python这个目录应当做为一个Python包看待.(若你是一个Python初学者,参阅Python官方文档中的more about packages.)
  • mysite/settings.py:当前Django项目的配置文件.Django settings将告诉你这些设置是如何工作的.
  • mysite/urls.py: 当前Django项目的URL声明, 是你用Django制作的网站的目录.你可以在URL dispatcher获得更多关于URLs的内容.
  • mysite/wsgi.py:一个让WSGI兼容web服务器支持你的项目的入口点. 访问How to deploy with WSGI以获取更多细节.

数据库设置(Database setup)

现在,打开mysite/settings.py.这是一个常见的Python模块,其中用一些模块级变量代表Django的设置.

默认情况下,Django使用SQLite作为数据库.若你刚接触数据库,或者刚对使用Django感兴趣,那么这将是最简单的选择.SQLite内置于Python,因此你并不需要安装任何其他东西就能使用.不过,当你开始你的第一个真正的项目时,你可能会想要使用一些更为强大的数据库(例如PostgreSQL),来避免数据库切换带来的一系列让人头疼的问题.

若你希望使用其他的数据库,首先安装合适的database bindings,然后修改DATABASES ‘default’项中的下列键,使其和你的数据库连接设置匹配:

  • ENGINE – 可以取‘django.db.backends.sqlite3’,
    ‘django.db.backends.postgresql_psycopg2’,
    ‘django.db.backends.mysql’,
    ‘django.db.backends.oracle’中的一项.另外一些后端同样可用.
  • NAME – 你的数据库的名字.若你使用的是SQLite,数据库将是你计算机中的一个文件,此时NAME应当是包括文件名的绝对路径.默认值os.path.join(BASE_DIR, 'db.sqlite3')将把文件存储在你的项目目录下.

若你使用SQLite以外的数据库,必须添加一些额外的设置,如USER,PASSWORD,HOST.你可以查阅DATABASES相关的参考文档以获取更多细节.

Note
若你正在使用PostgreSQL或MySQL,你需要保证你此时已经创建了一个数据库.你可以在你的数据库的交互提示下使用“CREATE DATABASE database_name;”来创建数据库.

若你正在使用SQLite,你不需要事先创建任何东西,数据库文件将在需要时被自动创建.

编辑mysite/settings.py时,将TIME_ZONE设置为你当前的时区.

同时,需要注意的是文件顶端有INSTALLED_APPS设置.这存储着所有这个Django实例中已经激活的Django应用的名字.应用(app)可以被多个项目使用,你可以将其打包并发布给他人使用.

默认情况下,INSTALLED_APPS包含下列Django自带的应用:

  • django.contrib.admin – 管理页面.你会在本教程的第二部分中使用到这个页面.
  • django.contrib.auth – 一个认证(authentication)系统.
  • django.contrib.contenttypes – 一个内容类型(content types)框架.
  • django.contrib.sessions – 一个会话(session)控制框架.
  • django.contrib.messages – 一个消息(messaging)框架.
  • django.contrib.staticfiles – 一个用于用于管理静态文件(static files)的框架.

这些应用将被默认安装以便于一些常见的情况下的使用.

部分以上应用需要使用至少一张数据库中的表,因此我们需要在使用前在数据库中创建这些表.你可以运行下面这条命令来创建:

$ python manage.py migrate

migrate命令在INSTALLED_APPS设置中查找并根据mysite/settings.py文件中的数据库设置创建所有需要的数据库.同时数据库随应用进行迁移(即migration,我们会在后面讲解数据库迁移).你将会看到一个指示每一个迁移情况的消息.假如你有兴趣的话,可以运行你的数据库的命令行客户端,输入\dt(PostgreSQL),SHOW TABLES;(MySQL),或者.schema(SQLite)来显示Django创建的数据表.

极简主义
就像我们之前说的那样,默认的应用已经为了处理常见情况而被包含,但并不是所有人都需要他们.假如你并不需要其中的某一个或者全部,你只需要在运行migrate之前将他们注释掉或直接删除.migrate命令只会对INSTALLED_APPS中的应用进行迁移.

开发用服务器(The development server)

现在,让我们来确认你的Django项目能否运行.先转到外层的mysite目录(如果当前不是在外层mysite目录下的话),然后运行下面的命令:

$ python manage.py runserver

你将在命令行中看到类似下面的输出:

Performing system checks...

System check identified no issues (0 silenced).
July 16, 2015 - 03:44:21
Django version 1.8.3, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

你已经打开了Django开发服务器.这是一个完全使用Python写成的轻量Web服务器,我们将其包含在Django中,使你在正式部署前不需要配置类似Apache的生产环境就能进行快速开发.

必须要指出是,不要在任何生产环境下使用这个服务器.这个服务器只为开发时的使用而设计.(毕竟我们的业务是制作Web框架而不是Web服务器).

现在,服务器已经在运行了,使用浏览器访问http://127.0.0.1:8000/,你将看到一个”Welcome to Django”页面.

修改端口
默认情况下,runserver命令将在内部IP的8000端口启动开发服务器.
如果你想要改变端口,你可以通过命令行参数进行设置.比如说这条命令将在8080端口启动服务器.

$ python manage.py runserver 8080

如果想要改变服务器的IP,将IP地址和端口一起传入即可.若想要监听所有公共IP(在你想知道当前网络中所有计算机的运行状况的时候将会很有用),可以使用:

$ python manage.py runserver 0.0.0.0:8000

完整的开发服务器相关文档可以在runserver参考中找到.

runserver的自动重载
开发服务器会按需要为每个请求自动重载Python代码.因此当你修改代码后,不需要重启服务器就使其生效.不过一些类似添加文件的行为不会出发重启,这些情况下你应当重新启动服务器.

创建model(Creating models)

现在你的环境(一个项目)已经设置完毕,可以开始干活了.

你在Django中写的每一个应用都分别由一个遵循某些约定的Python包组成.Django自带功能会自动生成一个app的基本目录结构,因此你可以专注于编写代码而不是创建目录.

项目(Projects) vs. 应用(Apps)
一个project和一个app之间有什么不同呢?一个app是一个用来实现某种功能的Web应用(比如说网络日志系统,一个包含公共记录的数据库或者一个简单的投票应用).而一个project是配置选项和具体网页的app的集合.一个project可以包含多个app.而一个app也可以在多个project中出现.

你可以把应用放在Python搜索路径中的任何位置.在本教程中,我们在manage.py文件所在路径创建app以使得你的应用可以作为顶级模块而不是mysite的子模块导入.

创建你的应用前,保证你当前处于与manage.py相同的路径,然后输入这条命令:

$ python manage.py startapp polls

这将创建一个polls目录,结构如下:

polls
├── admin.py
├── __init__.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py

这个目录结构用于存储poll应用.

用Django编写你自己的Web应用的第一步是定义你自己的models(实质上就是你的数据库布局和额外的元数据).

哲学
一个model是关于你的数据的”真理”的单一且明确的来源.它包含你所存储的数据的必要字段和行为.Django遵循DRY Principle,其目的是在一个地方定义你的数据模型并从中自动派生事物.

和 Ruby On Rails 等框架不同,Django 的model包含迁移(migration)工具,迁移完全由你的model文件派生,其实质为一个能让Django通过滚动来更新你的数据库架构,使之匹配你当前model的历史记录.

在我们的简单投票应用中,我们将创建两个model:QuestionChoice.一个Question含有一个问题和一个发布日期.一个Choice含有两个字段:选项的文本和当前票数.每一个Choice都与一个Question组织在一起.

这些概念将以简单的Python类表示.编辑polls/models.py以使其内容像下面这样:

#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')

class Choice(models.Model):
    question = models.ForeignKey(Question)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

这段代码很直观,每个model以一个派生自django.db.models.Model的类来表示.每一个model有一系列代表model中数据库字段的类变量.

每一个字段代表一个Field类的实例,例如CharField代表字符类型字段,DataTimeField代表日期类型字段.这告诉Django每个字段中存储的分别是哪一种数据.

每个Field实例的名字(比如question_text或是pub_date)是每个字段的名字,需要以方便机器识别的格式(in machine-friendly format)表示.你可以在你的Python代码中将使用它们,而你的数据库则将其作为列名使用.

你可以选择使用Field的第一个参数来指定一个可读性更好的名称.如果这个参数没有指定,Django将使用”机器可读”的名字.在当前这个例子中,我们只为Question.pub_date定义了一个”人类可读”的名称.对于这个model的其他字段,他们的”机器可读”名称同样满足”人类可读”,故不再指定.

一些Field类有必须提供的参数.例如CharField就要求你提供max_length.这不仅在数据库框架中有用到,之后我们也会看到它在验证中的使用.

一个Field有多个可选的参数;在这个例子中,我们将votesdefault值设置为0.

最后,要注意使用ForeignKey将定义一个关系.这将告诉Django每个Choice与一个Question关联.Django支持所有常见的数据库关系:多对一,多对对以及一对一.

激活model(Activating models)

这一段简单的model代码给予Django许多信息,使得Django能够做到:

  • 为这个应用(app)创建一个数据库框架(CREATE TABLE声明).
  • 创建一个Python数据库访问API来访问QuestionChoice对象.

但是首先我们需要告诉我们的项目,polls应用已经安装.

哲学
Django应用是可插拔的:你可以在多个Django项目中使用同一个app,同时你可以分发应用,因为他们并没有和特定的Django设备绑定.

再次编辑mysite/settings.py文件,然后修改INSTALLED_APPS设置使其包含字符串’polls’.编辑后的文件就像这样:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'polls',
)

现在,Django已经发现polls应用并将其包含.接着运行下面这条命令:

$ python manage.py makemigrations polls

你会看到类似这样的提示:

Migrations for 'polls':
  0001_initial.py:
    - Create model Choice
    - Create model Question
    - Add field question to choice

通过运行makemigrations,你通知Django你刚对你的model做了些更改(在这个例子中,你添加了一个新的model)并且你想要让这些改变作为migration存储.

迁移(Migration)是Django用来存储关于你的model(也就算数据库结构)的变化的一个文件.如果有兴趣的话,你也查看你的新model的迁移,它被储存为polls/migrations/0001_initial.py.虽然你多半并不会在每次Django产生一个迁移文件时都查看它,但是它们被设计为以”人类可编辑”的方式表示,方便你手动调节.

这里有一个能自动为你运行migration并管理数据库框架的命令:这条命令就是migrate.我们接下来就会讲到这条命令,但是首先,我们来看一下哪些迁移会运行.sqlmigrate命令以迁移名作为参数并返回它们对应的SQL语句:

$ python manage.py sqlmigrate polls 0001

你将看到类似这样的输出(为了更易阅读,我们已经对其格式进行修改):

BEGIN;
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);
CREATE TABLE "polls_question" (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;

COMMIT;

注意下列事项:

  • 实际输出决定于你所使用的数据库.上面的样例由PostgreSQL生成.
  • 数据表的名字以结合app名(polls)和model名的小写形式(question 和 choice)的方式自动生成(你可以覆盖这个行为).
  • 主键(ID)会自动添加.(你也可以覆盖这个行为).
  • 通常情况下,Django会在外键名称后加上“_id”.(是的,你同样可以覆盖这个行为).
  • 外键关系由FOREIGN KEY约束显式确定.不需要考虑DEFERRABLE部分,那只是告诉PostgreSQL在事务结束前不要检查外键关系.
  • 他会为你使用的数据库进行适配,也就是说,会自动替你处理那些数据库特有的部分比如auto_increment(MySQL),serial(PostgreSQL),或是primary key autoincrement(SQLite).同时也会替你处理字段名的引用,无论你用的是双引号还是单引号.
  • sqlmigrate命令不会真的在你的数据库上运行迁移–它只是将其打印在屏幕上来让你知道Django认为需要哪些SQL语句.这在检查Django将要做些什么或着数据库管理员要求提供改动的SQL脚本时将会很有用.

有兴趣的话,你也可以运行python manage.py check;这条命令将检查你项目中的所有问题而不会产生迁移或者改动数据库.

现在,再次运行migrate来在你的数据库中创建这些model对应的表.

$ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, messages
  Apply all migrations: admin, contenttypes, polls, auth, sessions
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying <migration name>... OK

migrate命令将找到所有还未应用的迁移(Django使用一个存储在数据库中的叫做django_migrations的特殊表来监测哪些迁移已经应用)并将其应用到你的数据库–其本质上是将你对model的改动同步到数据库结构.

迁移(migrations)功能十分强大,让你能够随着项目的开发不断修改model而不需要删除在创建数据库或者其中的表.它专注于在不丢失数据的情况下升级你的数据库.我们会在本教程的后续部分更为深入的讲解这些,现在你只需要记住修改model的三个步骤:

  • models.py中修改你的model.
  • 运行python manage.py makemigrations来为这些改动创建迁移.
  • 运行python manage.py migrate来将这些改动应用到数据库.

将创建和应用迁移分为两步是因为你会将迁移和你的app一起提交到你的版本控制系统(译注,这样其他人clone到本地之后就能通过python manage.py migrate来创建所需的数据库了);它们不但使你的开发更容易,而且对于其他开发者和生产有很大帮助.

django-admin documentation中关于manage.py这个工具的功能的全部信息.

使用API(Playing with the API)

现在,让我们进入交互式Python shell使用Django提供的API.调用Python shell可以使用这条命令:

$ python manage.py shell

我们使用这条命令而不是直接输入”python”,因为manage.py设置了DJANGO_SETTINGS_MODULE环境变量,这个环境变量会告诉Djangomysite/settings.py的导入路径(以Python路径形式).

绕过manage.py
如果你不想使用manage.py,这没问题.只要将DJANGO_SETTINGS_MODULE环境变量设置为mysite.settings就可以了.打开一个Python shell,然后设置Django:

>>> import django
>>> django.setup()

假如产生一个AttributeError,你可能正在使用和本教程版本不一致的Django.你需要切换到较旧的教程或是更新的Django版本.

你必须在与manage.py相同的目录下运行python,或者保证那个目录在Python路径中,从而使得import mysite能够运行.

更多的相关信息在 django-admin documentation.

当你进入shell之后,可以探索数据库API:

>>> from polls.models import Question, Choice   # Import the model classes we just wrote.

# No questions are in the system yet.
>>> Question.objects.all()
[]

# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

# Now it has an ID. Note that this might say "1L" instead of "1", depending
# on which database you're using. That's no biggie; it just means your
# database backend prefers to return integers as Python long integer
# objects.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() displays all the questions in the database.
>>> Question.objects.all()
[<Question: Question object>]

等一下.< Question: Question object >真是一种没有帮助的对象表示方式,让我们来修复这个问题.修改 Question model(在polls/models.py文件中)并为QuestionChoice添加_str_()方法:

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

class Question(models.Model):
    # ...
    def __str__(self):              # __unicode__ on Python 2
        return self.question_text

class Choice(models.Model):
    # ...
    def __str__(self):              # __unicode__ on Python 2
        return self.choice_text

为你的model添加_str_()方法是很重要的,不但方便你在交互提示中处理,也因为对象的表示将用于整个Django自动生成的管理.

_str_还是_unicode_?
在Python 3 中,很简单,使用_str_()就好了.

在Python 2 中,你需要定义一个返回unicode类型值的方法_unicode_().Django 中的model有一个默认的_str_()方法会调用_unicode_()并将结果转换到UTF-8编码的bytestring.这意味着unicode(p)将会返回一个Unicode字符串而str(p)将返回一个UTF-8编码的bytestring.而Python的行为相反:object具有一个_unicode_方法会调用_str_并将结果翻译为ASCII编码的bytestring.
这个区别会导致混乱.

如果这些对你来说难以理解,使用Python 3吧.

你可以发现这些都是普通的Python方法.让我们添加一个方法来作示范:

#polls/models.py
import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

要注意分别添加import datetimefrom django.utils import timezone来引用Python的标准datetime模块和Django的django.utils.timezone中时区相关的工具.若你对Python中的时区处理不熟悉,你可以在time zone support docs学到相关知识.

将修改保存并再次使用python manage.py shell打开一个新的Python交互式shell:

>>> from polls.models import Question, Choice

# Make sure our __str__() addition worked.
>>> Question.objects.all()
[<Question: What's up?>]

# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
[<Question: What's up?>]
>>> Question.objects.filter(question_text__startswith='What')
[<Question: What's up?>]

# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
[]

# Create three choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# Choice objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> q.choice_set.count()
3

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]

# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

关于model关系的信息可以查看Accessing related objects.关于如何使用双下划线来通过API进行字段查询,可以查看Field lookups.关于数据库API的全部细节,查看我们的Database API reference.

当熟悉API之后,查看本教程的第二部分来使得Django的自动管理页面运作.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值