Django 14天從小白到進階- Day2 搞定Models組件


本節內容

 

  • 路由系統
  • models模型
  • admin 
  • views視圖
  • template模板

 

 

引子

講django的models之前, 先來想一想, 讓你通過django操作數據庫,你怎么做? 做苦思冥想,可能會這樣寫。

import pymysql


def index(request):
    # 創建連接
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='alex123', db='luffy_dev')
    # 創建游標
    cursor = conn.cursor()

    cursor.execute("select username,email,mobile from web_account")
    data_set = cursor.fetchall()

    cursor.close()
    conn.close()

    return HttpResponse(data_set)

 

很方便就實現了從數據庫里取數據,事實上,很多人確實就是這么做的。但這樣做會帶來2個問題

  • SQL注入危險,因為有的時候你操作數據庫的語句不是寫死在代碼里的,而是通過前端傳參數拼接的,這就給黑客有了可趁之機,通過拼接參數實現sql注入。
  • 語句跟代碼揉在一起了,增加后續維護成本

 

那怎么辦呢?ORM提供了新思路。

什么是ORM呢?

對象關系映射(Object Relational Mapping),它的實質就是將關系數據(庫)中的業務數據用對象的形式表示出來,並通過面向對象(Object-Oriented)的方式將這些對象組織起來,實現系統業務邏輯的過程。

在ORM過程中最重要的概念是映射(Mapping),通過這種映射可以使業務對象與數據庫分離。從面向對象來說,數據庫不應該和業務邏輯綁定到一起,ORM則起到這樣的分離作用,使數據庫層透明,開發人員真正的面向對象。

上面的解釋有點蒙蔽對不?其實你只需要抓住2個關鍵詞, “映射” 和 “對象”,就能知道orm是什么干什么的了。

  • 映射(Mapping) —— 把表結構映射成類
  • 對象 —— 像操作類對象一樣,操作數據庫里的數據

映射

看下面的圖,就是直觀的例子,把右邊的表結構映射成了左邊的類

 Sql語句到對象

ORM可以使你不用再寫原生SQL, 而是像操作對象一樣就可以實現對表里數據的增刪改查

 

好棒棒,媽媽再也不用逼你寫原生sql啦!

但是不要開心太早,ORM確實提高了開發效率,並且降低了數據操作與代碼之間的耦合,不過有利就有弊,我們總結一下orm的優缺點。

優點:

  1. 實現了代碼與數據操作的解耦合
  2. 不需自己寫原生sql, 提高開發效率
  3. 防止SQL注入, 通過對象操作的方式,默認就是防止sql注入的。

缺點:

  1. 犧牲性能, 對象到原生SQL勢必會有轉換消耗,對性能有一定的影響 
  2. 復雜語句力不從心, 一些復雜的sql語句,用orm對象操作的方式很難實現,就還得用原生sql

 

講Django為什么說ORM? 哈,  好啦,是時候該引出主角啦,因為Django的models基於架構ORM實現的。

Models模型

Django 的models把數據庫表結構映射成了一個個的類, 表里的每個字段就是類的屬性。我們都知道數據庫有很多字段類型,int,float,char等, Django的models類針對不同的字段也設置了不同的類屬性。

AutoField         #An IntegerField that automatically increments according to available IDs
BigAutoField	  #A 64-bit integer, guaranteed to fit numbers from 1 to 9223372036854775807.
BigIntegerField   #-9223372036854775808 to 9223372036854775807
BinaryField		  #A field to store raw binary data. It only supports bytes assignment
BooleanField    
CharField
DateField         #e.g 2019-04-27 
DateTimeField	  #e.g 2019-04-27 17:53:21
DecimalField	  
DurationField     #storing periods of time ,e.g [DD] [HH:[MM:]]ss[.uuuuuu]"
EmailField
FileField		  #存儲文件
FloatField
ImageField		  #Inherits all attributes and methods from FileField, but also validates that the uploaded object is a valid image.
IntegerField
GenericIPAddressField #IP地址,支持ipv4 
NullBooleanField	  #Like a BooleanField, but allows NULL as one of the options 
PositiveIntegerField  #Like an IntegerField, but must be either positive or zero (0). Values from 0 to 2147483647 
PositiveSmallIntegerField #only allows positive  values from 0 to 32767
SlugField # A slug is a short label for something, containing only letters, numbers, underscores or hyphens. 
SmallIntegerField 
TextField   #A large text field.
TimeField   #A time, represented in Python by a datetime.time instance.
URLField 
UUIDField   #A field for storing universally unique identifiers. Uses Python’s UUID class.

 

除了普通的表字段,針對外鍵也有映射

ForeignKey  # 外鍵關聯
ManyToManyField  #多對多

OneToOneField  # 1對1 

  

好啦,接下來就用django的orm來設計一個博客表。

需求

  1. 每個用戶有自己的賬戶信息
  2. 用戶可以發文章
  3. 文章可以打多個標簽

根據需求,我們設計3張表

注意Article表和Tag表是屬於多對多關系,什么是多對多?即一個文章有多個標簽,一個標簽又可以屬於多個文章。 

比如上圖的Article表中id為3的文章 ,它的標簽是4,26, 即投資、大文娛、社交, 你看“投資”這個標簽同時還屬於文章2。 這就是多對多關系 , 即many to many . 

那這種多對多的關系如何在表中存儲呢?難道真的像上圖中一樣,在Article表中加個tags字段,關聯Tag表里的多條數據,通過逗號區分?

這倒確實是個解決辦法。但是也有問題,一個字段里存多條紀錄的id,就沒辦法做查詢優化了。比如不能做索引等。

所以若想實現多對多關系的高效存儲+查詢優化,可以在Article and Tag表之間再搞出一張表。

這樣是不是就實現了多對多關聯?

yes, 沒錯, django也是這么做的, django 有個專門的字段,叫ManyToManyField, 就是用來實現多對多關聯的,它會自動生成一個如上圖一樣的第3張表來存儲多對多關系。

 

正式的表結構

from django.db import models

# Create your models here.


class Account(models.Model):
    username = models.CharField(max_length=64,unique=True)
    email = models.EmailField()
    password = models.CharField(max_length=128)
    register_date = models.DateTimeField("注冊日期",auto_now_add=True)
    signature = models.CharField(verbose_name="簽名",max_length=128,blank=True,null=True)


class Article(models.Model):
    """文章表"""
    title = models.CharField(max_length=255,unique=True)
    content = models.TextField("文章內容")
    account = models.ForeignKey("Account",verbose_name="作者",on_delete=models.CASCADE)
    tags = models.ManyToManyField("Tag",blank=True)
    pub_date = models.DateTimeField()
    read_count = models.IntegerField(default=0)


class Tag(models.Model):
    """文章標簽表"""
    name = models.CharField(max_length=64,unique=True)
    date = models.DateTimeField(auto_now_add=True)

  

我們發現,每個字段其實都是一個獨立的對象,一張表其實是很多類的組合。

上面好多字段里還跟了些參數,我們來看以下常用的:

null 		#If True, Django will store empty values as NULL in the database. Default is False.
blank		#If True, the field is allowed to be blank. Default is False.

db_column	#The name of the database column to use for this field. If this isn’t given, Django will use the field’s name.
db_index	#If True, a database index will be created for this field. 
default  	#The default value for the field. This can be a value or a callable object. If callable it will be called every time a new object is created.
editable    # django admin中用,后面講
help_text	# django admin中用,后面講
primary_key	# If True, this field is the primary key for the model.
unique 		#If True, this field must be unique throughout the table
unique_for_date    #Set this to the name of a DateField or DateTimeField to require that this field be unique for the value of the date field. For example, if you have a field title that has unique_for_date="pub_date", then Django wouldn’t allow the entry of two records with the same title and pub_date.

unique_for_month   #Like unique_for_date, but requires the field to be unique with respect to the month.
unique_for_year    
verbose_name	#A human-readable name for the field. If the verbose name isn’t given, Django will automatically create it using the field’s attribute name

 

還有幾個特殊的字段屬性需要單獨介紹下

 

choices

An iterable (e.g., a list or tuple) consisting itself of iterables of exactly two items (e.g. [(A, B), (A, B) ...]) to use as choices for this field.

The first element in each tuple is the actual value to be set on the model, and the second element is the human-readable name.

class Student(models.Model):
	YEAR_IN_SCHOOL_CHOICES = (
	    ('FR', 'Freshman'),
	    ('SO', 'Sophomore'),
	    ('JR', 'Junior'),
	    ('SR', 'Senior'),
	)
    year_in_school = models.CharField(
        max_length=2,
        choices=YEAR_IN_SCHOOL_CHOICES,
        default=FRESHMAN,
    )

 

ForeignKey.on_delete

當一條記錄關聯的外鍵紀錄被刪除時,django 也會根據外鍵關聯限制的配置來決定如何處理當前這條紀錄。舉例,如果你有個可以為null的外鍵關聯,並且你想在本紀錄關聯的數據被刪除時,把當前紀錄的關聯字段設為null,那就配置如下

user = models.ForeignKey(
    User,
    on_delete=models.SET_NULL,
    blank=True,
    null=True,
) 

這個on_delete就是決定在關聯對象被刪除時,如何處理當前紀錄的,常用的參數如下:

  • CASCADE——Cascade deletes. Django emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey.
  • PROTECT——Prevent deletion of the referenced object by raising ProtectedError, a subclass of django.db.IntegrityError.
  • SET_NULL——Set the ForeignKey null; this is only possible if null is True.
  • SET_DEFAULT——Set the ForeignKey to its default value; a default for the ForeignKey must be set.

 

配置Django數據庫連接信息

Django支持多種數據庫,Sqlite、Mysql、Oracle、PostgreSQL,默認的是小型文件數據庫Sqlite

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

 

咱們是干大事的人,怎么也得用個Mysql呀, 改成mysql 也so easy.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'my_db',
        'USER': 'mydatabaseuser',
        'PASSWORD': 'mypassword',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}

 

不過注意,python3 連接mysql的得使用pymysql,MysqlDB模塊300年沒更新了,但django默認調用的還是MySQLdb, so pymysql有個功能可以讓django以為是用了MySQLdb. 即在項目目錄下的__init__.py中加上句代碼就好

import pymysql

pymysql.install_as_MySQLdb()

 

不加的話,一會連接數據時會報錯噢 。

 

同步數據庫  

你在ORM定義的表結構如何同步到真實的數據庫里呢? 只需2條命令。但django只能幫你自動創建表,數據庫本身還是得你自己來。

create database my_db charset utf8;  

  

好了,可以同步了,說好只需2步。

1. 生成同步文件, django自帶一個專門的工具叫migrations, 負責把你的orm表轉成實際的表結構,它不旦可以幫自動創建表,對表結構的修改,比如增刪改字段、改字段屬性等也都能自動同步。只需通過下面神奇的命令。

python manage.py makemigrations  

 不出意外的話,會顯示類似以下信息

$ python manage.py makemigrations
Migrations for 'app01':
  app01/migrations/0001_initial.py
    - Create model Account
    - Create model Article
    - Create model Tag
    - Add field tags to article

 

此時你會發現,你的app下的migrations目錄里多了一個0001_initial.py的文件 ,這個文件就是因為你這條命令而創建的,migrations工具就會根據這個文件來創建數據庫里的表。

2. 同步到數據

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, app01, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying app01.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK
(venv_django2) Alexs-MacBook-Pro:mysite alex$ 

 

此時登錄你的數據庫,會發現創建了好多張表

mysql> show tables;
+----------------------------+
| Tables_in_luffy_dev2       |
+----------------------------+
| app01_account              |  #對應Account表
| app01_article              |	#對應Article表
| app01_article_tags         |	#自動創建的Article to Tag的多對多關聯表
| app01_tag                  |	#對應Tag表
| auth_group                 |  #下面這些,都是django 自帶的表,這個是自動用戶系統的組
| auth_group_permissions     |	#自帶的組與權限的多對多關聯表
| auth_permission            |	#自帶權限表
| auth_user                  |	#用戶表
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |	#現在你的無法理解 	
| django_content_type        |	#現在你的無法理解 
| django_migrations          |	#紀錄migartions工具同步紀錄的表
| django_session             |	#現在你的無法理解 
+----------------------------+
14 rows in set (0.00 sec)

  

好啦,表結構也有了,我們可以往里面插數據啦。

之前說好的是可以不用SQL語句的,一點不騙你。

 

用orm對表數據進行增刪改查

先進入已經連接好數據庫的django python環境

(venv_django2) Alexs-MacBook-Pro:mysite alex$ python manage.py shell  
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 26 2016, 10:47:25) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 
>>> from app01 import models

  

創建

創建數據簡單的令人發指

 

 

 

filter 支持很多的過濾條件,我們來看下:

 

 

contains

包含,相當於sql的like條件

Entry.objects.get(headline__contains='Lennon')

SQL equivalent:

SELECT ... WHERE headline LIKE '%Lennon%';

Note this will match the headline 'Lennon honored today' but not 'lennon honored today'.

icontains  大小寫不敏感  

  

in

In a given iterable; often a list, tuple, or queryset.

Entry.objects.filter(id__in=[1, 3, 4])

SQL equivalent:

SELECT ... WHERE id IN (1, 3, 4);

You can also use a queryset to dynamically evaluate the list of values instead of providing a list of literal values:

inner_qs = Blog.objects.filter(name__contains='Cheddar')
entries = Entry.objects.filter(blog__in=inner_qs)

This queryset will be evaluated as subselect statement:

SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')

  

gt

Entry.objects.filter(id__gt=4)

SQL equivalent:

SELECT ... WHERE id > 4;

gte
Greater than or equal to.

lt
Less than.

lte
Less than or equal to.

startswith
Case-sensitive starts-with.

Entry.objects.filter(headline__startswith='Lennon')

SQL equivalent:

SELECT ... WHERE headline LIKE 'Lennon%';

SQLite doesn’t support case-sensitive LIKE statements; startswith acts like istartswith for SQLite  

istartswith
Case-insensitive starts-with.

endswith
Case-sensitive ends-with.

iendswith
Case-insensitive ends-with

  

range
區間過渡,可對數字、日期進行過濾

import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))

SQL equivalent:

SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';

Warning!

Filtering a DateTimeField with dates won’t include items on the last day, because the bounds are interpreted as “0am on the given date”. If pub_date was a DateTimeField, the above expression would be turned into this SQL:

SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00';
Generally speaking, you can’t mix dates and datetimes. 

  

date

For datetime fields, casts the value as date. Allows chaining additional field lookups. Takes a date value.  

Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))

year
For date and datetime fields, an exact year match. Allows chaining additional field lookups. Takes an integer year.

Entry.objects.filter(pub_date__year=2005)
Entry.objects.filter(pub_date__year__gte=2005)

SQL equivalent:

SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31';
SELECT ... WHERE pub_date >= '2005-01-01';

When USE_TZ is True, datetime fields are converted to the current time zone before filtering. 簡單解決辦法是把USE_TZ=False

month
For date and datetime fields, an exact month match. Allows chaining additional field lookups. Takes an integer 1 (January) through 12 (December).

Entry.objects.filter(pub_date__month=12)
Entry.objects.filter(pub_date__month__gte=6)

When USE_TZ is True, datetime fields are converted to the current time zone before filtering. This requires time zone definitions in the database.

SQL equivalent:

SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12';
SELECT ... WHERE EXTRACT('month' FROM pub_date) >= '6';

day
For date and datetime fields, an exact day match. Allows chaining additional field lookups. Takes an integer day.

Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3)

SQL equivalent:

SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3';
SELECT ... WHERE EXTRACT('day' FROM pub_date) >= '3';

  

week

New in Django 1.11.

For date and datetime fields, return the week number (1-52 or 53) according to ISO-8601, i.e., weeks start on a Monday and the first week contains the year’s first Thursday.

Example:

Entry.objects.filter(pub_date__week=52)
Entry.objects.filter(pub_date__week__gte=32, pub_date__week__lte=38)

week_day

For date and datetime fields, a ‘day of the week’ match. Allows chaining additional field lookups.

Takes an integer value representing the day of week from 1 (Sunday) to 7 (Saturday).

Example:

Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)

hour

For datetime and time fields, an exact hour match. Allows chaining additional field lookups. Takes an integer between 0 and 23.

Example:

Event.objects.filter(timestamp__hour=23)
Event.objects.filter(time__hour=5)
Event.objects.filter(timestamp__hour__gte=12)

SQL equivalent:

SELECT ... WHERE EXTRACT('hour' FROM timestamp) = '23';
SELECT ... WHERE EXTRACT('hour' FROM time) = '5';
SELECT ... WHERE EXTRACT('hour' FROM timestamp) >= '12';同  

同時,還支持mintue,second

Event.objects.filter(time__minute=46)


Event.objects.filter(timestamp__second=31)

isnull

Takes either True or False, which correspond to SQL queries of IS NULL and IS NOT NULL, respectively.

Example:

Entry.objects.filter(pub_date__isnull=True)

SQL equivalent:

SELECT ... WHERE pub_date IS NULL;

regex

Case-sensitive regular expression match.

Example:

Entry.objects.get(title__regex=r'^(An?|The) +')

SQL equivalents:

SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; -- MySQL

SELECT ... WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c'); -- Oracle

SELECT ... WHERE title ~ '^(An?|The) +'; -- PostgreSQL

SELECT ... WHERE title REGEXP '^(An?|The) +'; -- SQLite  

iregex 大小寫不敏感    

   

改刪

# 批量修改
models.Account.objects.filter(username='elina').update(password="Luffy#21")

# 單條修改
obj = models.Account.objects.get(username='linux')
obj.username = 'python'
obj.save()


# 批量刪除
models.User.objects.get(password='oldboy').delete()

# 單條刪除
obj = models.User.objects.get(id=3)
obj.delete()

 

 

數據返回后的展示

values()

values( *fields**expressions)

Returns a QuerySet that returns dictionaries, rather than model instances, when used as an iterable.

>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values('id', 'name')
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

order_by()

order_by( *fields)

By default, results returned by a QuerySet are ordered by the ordering tuple given by the ordering option in the model’s Meta. You can override this on a per-QuerySet basis by using the order_by method.

Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')

The result above will be ordered by pub_date descending, then by headline ascending. The negative sign in front of "-pub_date"indicates descending order. Ascending order is implied. 

reverse()

Use the reverse() method to reverse the order in which a queryset’s elements are returned. Calling reverse() a second time restores the ordering back to the normal direction.

To retrieve the “last” five items in a queryset, you could do this:

my_queryset.reverse()[:5]

  

ORM對象操作

單表對象操作
o = models.Article.objects.all()[0]
o.tilte 

外鍵關聯
>>> o.account.username
'jack'
>>> o.account.username = rain

外鍵反向關聯操作
>>> a = models.Account.objects.get(username='alex')
>>> a.article_set.all()
<QuerySet [<Article: 你好,2018>]>
>>> a.article_set.select_related()
<QuerySet [<Article: 你好,2018>]>


多對多操作
>>> o = models.Article.objects.all()[1]
>>> o.tags.all()
<QuerySet [<Tag: 投資>, <Tag: 科技>]>


多對多反向操作
>>> t = models.Tag.objects.get(name="投資")
>>> t.article_set.all()
<QuerySet [<Article: 你好,2018>, <Article: 粉絲超過10萬后,我經歷了抖音盜號風波>]>

  

  

 

好啦,orm的操作先點到為止,后面學項目時再帶你搞復雜的。

練習題

  1. 基於前面課程設計的表結構,完成以下練習:
  2. 創建5條account和5條新tag紀錄
  3. 創建5條article信息,關聯上面的不同的用戶和tag
  4. 在account表里找到用戶名包含al的紀錄,然后把密碼改掉
  5. 在article表找到文章內容包含“電影”2個字的,把這些文章加上”大文娛”tag
  6. 把用戶elina發表的文章找出來,並且把作者都改成alex
  7. 找到用戶表里注冊日期在2018-04月,並且signature為空的紀錄
  8. 打到文章中標簽為“投資”的所有文章
  9. 找到每個月8號注冊的用戶
  10. 找到每年5月發表的文章 
  11. 找到2015-2017年5月發表的文章 
  12. 找到文章作者以’a’或’k’開頭的文章

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM