初识Django
环境 : Linux 7.4 Django 1.11+ Python 3.6
请求
用户 > 匹配url > urls.py路由 > 解析到视图 > views ,核心逻辑处理
通过视图views.py
-
通过QuerySet ---> modles --> DB数据库 ----> 返回数据给视图
-
通过模板进行渲染 --> template --> 返回HTML(JONS)页面 ---》 返到用户
-
通过views.py 直接返回给用户
MVC和MTV模式
Django的MTV模式本质是各组件之间为了保持松耦合关系,Django的MTV分别代表:
-
Model(模型):负责业务对象与数据库的对象(ORM)
-
Template(模版):负责如何把页面展示给用户
-
View(视图):负责业务逻辑,并在适当的时候调用Model和Template
此外,Django还有一个url分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template
locals() 返回给templates 渲染给 html 给用户
locals() #简写 ----》 {'data':data,'msg':msg} #正常写法
urls.py ----> #路由
from django.conf.urls import url
from django.contrib import admin
from hc import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'login/$', views.login),
#ip:port/ 后面的内容就是这个路由的结果 即 127.0.0.1:8000/login
views.py #视图
for django.shortcuts improt render
def login(request):
if request.method == 'GET':
data = 'hellow world'
a = 'this my django project'
return render(request, 'login.html', {'data': data, 'a': a})
#return render(request, 'login.html', locals())
#locals() == {'data': data, 'a': a} #简写
#写法 render(request, 'templates模板','是views的返回值,可简写为 locals() ')
templates #模板 即HTML页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hc</title>
</head>
<body>
<h1>{{ data }}</h1>
# {{ data }} 这种写法里面的 data 是上面views 视图的输出
<p>{{ a }}</p>
</body>
</html>
models
简单的访问数据的内容
urls.py --> models.py --> views.py --> login.html
login class UserInfo models.UserInfo.objects.all {%for i in sqlite%}
模块
models.py
#创建表和字段
class UserInfo(models.Model): #创建一个类。继承了models.Model类 ---> 固定写法
username = models.CharField(max_length=32,null=True)
password = models.CharField(max_length=32,null=True)
#CharField 由字符或者文本组成的数据,需要存储少量的文本的用,CharField()
#max_length --> 指定最大长度。null --> 是否允许为空
命令行执行两步: 修改和迁移
python manage.py makemigrations # 修改数据库
python manage.py migrate # 迁移数据库
解析:
当models.py 修改过后,创建表和字段结构。即需要重新数据
makemigrations #修改数据库
migrate # 迁移数据库 ,保存
视图
views.py
from hc improt models #加载刚创建的数据库
def login(request):
if request.method == 'GET':
obj_ite = models.UserInfo.object.all()
#这里的obj_ite就是一个queryset_list
#obj_ite = models.UserInfo.object.filter(username='test')
# .filter() 也是 queryset_list
#使用filter() 来指定某一数据
for i in obj_ite:
print i.username
print i.password
#通过这个queryset_list 经过for 循环来点(.) 出来对象的值
return render(request,'login.html',locals())
template 模板
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hc</title>
</head>
<body>
<h1> user </h1>
{% for i in obj_ite %} # 循环判断都是用 {% if..for %}
<div>
<span> {{ i.username }} </span>
<span> {{ i.password }} </span>
</div>
{% endfor %}
</body>
</html>
span 标签 <span> 标签被用来组合文档中的行内元素。
提示:请使用 <span> 来组合行内元素,以便通过样式来格式化它们。
注释:span 没有固定的格式表现。当对它应用样式时,它才会产生视觉上的变化。
sqlite 这是它的django自带的数据库
每个类会生成一个表
QuerySet 和 QuerySetList
QuerySet :
简单理解为 每条数据的对象,可以通过这个对象可以点(.) 出来它里面的值
QuerySetList :
这是个对象的列表,需要通过循环才能取值
queryset_list ===> object.all()
filter() ===> where
创建好数据后需要手动录入数据,需要在上方找到一个数据提交按钮 ,点击后才是写入数据
如果 sqlite DB 没有显示内容则需要安装一个东西
链接
https://blog.csdn.net/u013155359/article/details/81874665
django 基本命令
创建一个新的 django 项目django-admin.py startproject <project_name>
创建一个新的 django 应用python manage.py startapp <app_name>
启动djangopython manage.py runserver
---> 默认为127.0.0.1:8000 可以是0.0.0.0:8000
数据同步操作
django 1.7以前的操作是python manage.py syncdb
django 1.7以后的版本都是这样
python manage.py makemigrations #修改数据库
python manage.py migrate #迁移数据库
创建超级管理员adminpython manage.py createsuperuser
# create super user
修改 用户密码可以用:python manage.py changepassword username
django 项目环境终端 ,可以用来测试
python manage.py shell #对项目
python manage.py dbshell #对数据库
更多命令python manage.py
路由
urls.py
urlpatterns = [
url(正则表达式, views视图函数,参数,别名),
]
参数说明:
一个正则表达式字符串
一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
可选的要传递给视图函数的默认参数(字典形式)
一个可选的name参数
这是总路径
from django.conf.urls import url,include
from django.contrib import admin
from hc import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'login/', views.login),
url(r'^api', include('hc01.urls')) #include 用于拼接下级urls
]
以下都是子路径里的内容
从另个一应用导入
from django.conf.urls import url
from hc01 import views
urlpatterns = [
url(r'^1.html', views.list), #这样和上面的拼接,
url(r'^2.html', views.list), # localhost/api/2.html
url(r'^3.html', views.list), # localhost/api/3.html
]
hc01 应用里的 views.py
from django.shortcuts import render,HttpResponse
#HttpResponse() 不用写页面,直接返回括号里的内容
# Create your views here.
def hc01(request):
return HttpResponse('is hc01')
def hc02(request):
return HttpResponse('is hc02')
def hc03(request):
return HttpResponse('is hc03')
#HttpResponse() 不用写页面,直接返回括号里的内容
url 补充:
页面传参时
hc01 应用里的urls.py
from django.conf.urls import url
from hc01 import views
urlpatterns = [
url(r'^(?P<num>[0-9]+)/1.html', views.list),
# (?P<num>[0-9]+) 正则匹配,匹配1-9多个数字'+'
url(r'^(?P<num>[0-9]+)/2.html', views.list),
# 正则匹配前面可以不加'/'。 加了会有警告
url(r'^(?P<num>[0-9]+)/3.html', views.list),
]
hc01 应用里的 views.py
from django.shortcuts import render,HttpResponse
#HttpResponse() 不用写页面,直接返回括号里的内容
# Create your views here.
def hc01(request,num):
print num
return HttpResponse('is hc01')
def hc02(request,num):
print num
return HttpResponse('is hc02')
#传进来的参数num 都是字符串。如果是数字需要加int(num)
def hc03(request,num):
print num
return HttpResponse('is hc03')
#HttpResponse() 不用写页面,直接返回括号里的内容
template 模板
- 模板语言
if 、 for 、 jquery
等 - 模板继承
成对出现 if ,for
if
{if ...}
{endif}
for
{for i in ..}
{endfor}
{% load staticfiles %} #载入静态配置文件,这里只是路径
在 setings.pySTATIC_URL = '/static/'
#这里可以改
语法格式: {{obj|filter:param}}
1 add : 给变量加上相应的值
2 addslashes : 给变量中的引号前加上斜线
3 capfirst : 首字母大写
4 cut : 从字符串中移除指定的字符
5 date : 格式化日期字符串
6 default : 如果值是False,就替换成设置的默认值,否则就是用本来的值
7 default_if_none : 如果值是None,就替换成设置的默认值,否则就使用本来的值
实例:
value1="aBcDe"
{{ value1|upper }}<br>
value2=5
{{ value2|add:3 }}<br>
value3='he llo wo r ld'
{{ value3|cut:' ' }}<br>
import datetime
value4=datetime.datetime.now()
{{ value4|date:'Y-m-d' }}<br>
value5=[]
{{ value5|default:'空的' }}<br>
value6='<a href="#">跳转</a>'
{{ value6 }}
{% autoescape off %}
{{ value6 }}
{% endautoescape %}
{{ value6|safe }}<br>
{{ value6|striptags }}
value7='1234'
{{ value7|filesizeformat }}<br>
{{ value7|first }}<br>
{{ value7|length }}<br>
{{ value7|slice:":-1" }}<br>
value8='http://www.baidu.com/?a=1&b=3'
{{ value8|urlencode }}<br>
value9='hello I am yuan'
我的template
<!DOCTYPE html>
{% load staticfiles %} #这里载入jQuery ,在setings.py里面设置
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hc</title>
<script src="{% static 'jquery-3.3.1.min.js' %}"></script> #在指定位置载入
</head>
<body>
<h1> user meagess</h1>
{% for item in obj_sqlite %}
<div>
<span>{{ item.username | upper}}</span>
# '|'类似管道付,后面写的是一个方法、 upper:全部大写
<span>{{ item.password | add:100}}</span>
# add:100 是把前面的值增加100.前面的变量必须是数值
<p></p>
</div>
{% endfor %}
{{ abc }}
<br>
{{ abc | cut:' ' }} # 使用了cut 移除指定的字符
<br>
{{ d }}
<br>
{{ d | date:'Y - m - d' }} # 使用了date 把后端输出的时间格式化到前端
</body>
</html>
引入时间函数,才能调用。
import datetime
d = datetime.datetime.now() # .now() 为当前的时间
Dec. 17, 2018, 4:46 p.m.
模板继承
在 template 里面定义
base.html
总的页面。所用子页面继承他的内容
<div>
{% block content %}
#这里是你需要的页面
{% endblock %}
</div>
login.html #子页面 里面写该页面需要的内容
{% extends 'base.html' %}
{% block content %}
#你需要的内容
{% endblock %}
simple_tag
当django的原生方法不够用时, 我可以自定方法来使用 这就是simple_tag
首先创建包 必须是 templatetags 这个名字
在这个包下创建一个py文件。名字可以自定义
创建了一个 my_tag.py 文件
my_tag.py
from django.template improt Library #首先导入模块 固定导入
register = Library() #这个实例化的对象必须叫 register
@register.filter # filter 方法只能传递两个参数。 这里就是装饰器了
def filter_func(x , y): #.filter 方法只能传递两个参数,多了就不行
return x+y
@register.simple_tag # simple_tag 它可以接收多个参数
def simple_func(t,a,b,c): # .simple_tag 方法 可以传递多个参数
return '2018-12-17'
这项目的名字 一定要再 setings.py 里面的 INSTALLED_APPS 里面
还有就是再html 里面去定义引用
{% load my_tag %}
#要再template 里去引入这个我创建的 my_tag.py 文件
{{ test|filter_func:'123' }}
# 传入两个参数,这里用来拼接,因为上面的函数定义了 return x+y
# test 当成第一个参数,'123' 当成第二个参数,传入 filter_func 这个方法里
{{ simple_func 't' 'a' 'b' 'c' }}
# 这里传入什么值 都return一个固定值,因为我方法里是这样定义了
simple_tag
总结一下
-
创建包必须的名字必须是 'templatetags' 这个名字
-
导入这个模块,Library模块是装饰器 'from django.template improt Library'
-
这个实例化的对象必须叫 'register' --> register = Library()
-
这项目的名字一定要再 setings.py 的 INSTALLED_APPS 里 有定义这个项目名
-
在template里面需要引用 写好的.py文件 '{% load my_tag %} '
Django - admin
view主要返回
render HttpResponse redirect
render 模板语言的渲染页面
HttpResponse
直接返回你想要的一个字符串,也可以是个对象
redirect 路由的跳转, 匹配括号内的地址
render函数
render(request, template_name[, context])
结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。
参数:
request:
用于生成响应的请求对象。
template_name:
要使用的模板的完整名称,可选的参数
context:
添加到模板上下文的一个字典。默认是一个空字典。
如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。
content_type:
生成的文档要使用的MIME类型。默认为DEFAULT_CONTENT_TYPE 设置的值。
status:
响应的状态码。默认为200。
总结:
render和redirect的区别:
1 . if render的页面需要模板语言渲染,需要的将数据库的数据加载到html,那么所有的这一部分除了写在yuan_back的视图函数中,必须还要写在login中,代码重复,没有解耦.
2 . the most important: url没有跳转到/yuan_back/,而是还在/login/,所以当刷新后又得重新登录.
admin
admin是django强大功能之一,它能共从数据库中读取数据,呈现在页面中,进行管理。默认情况下,它的功能已经非常强大,如果你不需要复杂的功能,它已经够用,但是有时候,一些特殊的功能还需要定制,比如搜索功能,下面这一系列文章就逐步深入介绍如何定制适合自己的admin应用。
如果你觉得英文界面不好用,可以在setting.py 文件中修改以下选项
LANGUAGE_CODE = 'en-us' #LANGUAGE_CODE = 'zh-hans'
python manage.py createsuperuser
# 创建后台超级用户 superuser
输入 用户名 和 密码
admin.py
from hc_learning improt models
# 把 我自己写的 models导入
两种方式
1 使用register的方法
admin.site.register(Book,MyAdmin)
2 使用register的装饰器
@admin.register(Book)
第一种
register
admin.site.register(models.UserInfo)
# 这样使用它自带的封装
还要需要把 app 注册到 settings.py 里面
掌握一些常用的设置技巧
list_display: 指定要显示的字段
search_fields: 指定搜索的字段
list_filter: 指定列表过滤器
ordering: 指定排序字段
这么用
class MyAdmin(admin.ModelAdmin):
list_display = ('name','username',"password") #括号内的是字段名
admin.site.register(models.UserInfo,MyAdmin) #MyAdmin 这里要把这个类```当参数传进去
Django ORM
ORM基础
如果要使用真 mysql 需要去settings 里面将DATABASES 修改成如下
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'books', #你的数据库名称
'USER': 'root', #你的数据库用户名
'PASSWORD': '', #你的数据库密码
'HOST': '', #你的数据库主机,留空默认为localhost
'PORT': '3306', #你的数据库端口
}
注:
NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建
USER和PASSWORD分别是数据库的用户名和密码。
设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。
然后,启动项目,会报错:no module named MySQLdb
这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是PyMySQL
所以,我们只需要找到项目名文件下的init,在里面写入:
需要在项目名文件下的init.py 在里面写入
import pymysql
pymysql.install_as_MySQLdb()
#重点
增加数据表 就在models.py 里面写
class UserInfo(models.Model):
name = models.CharField(max_length=32,null=True,blank=True,verbose_name='姓名')
username = models.CharField(max_length=32,null=True)
password = models.CharField(max_length=32,null=True)
# num = models.IntegerField(default=0) #定义了一个数字类型,默认值为0
def func(self):
return 'add you baba'
def __str__(self): #查询数据库 返回字符串
return self.name
class School(models.Model): #null=True,blank=True 要用就要一起用
name = models.CharField(max_length=32,null=True,blank=True,verbose_name='学校名')
#null=True 可以为空, blank 针对admin使用的时候这两个同时使用可以为空。verbose_name 显示信息
# mac = models.URLField(max_length=128,null=True,blank=True,verbose_name='地址')
# email = models.EmailField(max_length=128,null=True,blank=True,verbose_name='邮箱')
# true_false = models.BooleanField(max_length=128,null=True,blank=True,verbose_name='是否')
# date = models.DateField(verbose_name='时间')
# cla = models.ForeignKey(to='Class',default=1) #default='1',设置默认值 # ForeignKey 一对多类型。
#自动生成ID --> cla_id ,指向了Class 类,它里面会自己生成ID,是class的ID
def __str__(self):
return self.name
class Number(models.Model):
num = models.OneToOneField(to='UserInfo') # 一对一的一一对应关系
# OneToOne 类型对应 UserInfo ,每个学生都有一个学号
def __str__(self):
return self.num
class Class(models.Model):
xuexiao = models.ForeignKey(to='School', default=1)
name = models.CharField(max_length=128,null=True,blank=True,verbose_name='班级')
user = models.ManyToManyField(to='UserInfo',related_name='clauser') # 多对多关系
# class_to_userinfo --> id , UserInfo_id , class_id 三张表
def __str__(self):
return self.name
def __str__(self):
__str__()方法,返回一个好看的字符串就可以了:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> print Student('Michael')
Student object (name: Michael)
这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。
但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看:
>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
这是因为直接显示变量调用的不是__str__()
,而是__repr__()
,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。
解决办法是再定义一个__repr__()
。但是通常__str__()
和__repr__()
代码都是一样的,所以,有个偷懒的写法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
__str__()
返回用户看到的字符串,__repr__()
返回程序开发者看到的字符串,__repr__()
是为调试服务的。
增删改查-----查询
两种查询方式
第一种 .all()
第二种 .get(id=?) 或者 .filter() 里有 .first() 和 .last()
.get 取的是 queryset 集合
.filter 取的是 queryslit 列表
查的时候推荐用 .filter
models.School.objects.all()
#查询所有 是一个queryslit 里面每一个值都是queryset
b = models.School.objects.all()
#返回所有信息 all()出来的是 queryset_list
这个是个list的里面有很多的queryset
models.School.objects.all()
b = models.School.objects.all()
print (b) # 打印这个输出QuerySet
print (type(b)) #看这个是什么类别
for i in b:
print (i.name,type(i),i) #循环输出这个list里面的信息
print (b)
<QuerySet [<School: 你爸爸大学>, <School: 五道口职业学院>]>
print (type(b))
<class 'django.db.models.query.QuerySet'>
for --> print (i.name,type(i),i) # 这里输出的是 类
你爸爸大学 <class 'hc.models.School'> 你爸爸大学
五道口职业学院 <class 'hc.models.School'> 五道口职业学院
c = models.School.objects.get(id=1) # get通过id 拿到的是queryset 的值,只能取ID
print (c) ---> 你爸爸大学 <class 'hc.models.School'>
d = models.School.objects.filter(name='五道口职业学院').first()
e = models.School.objects.filter(name='你爸爸大学').last()
print (d,e) ---> 五道口职业学院, 你爸爸大学
d = models.School.objects.filter(name='abc').first()
e = models.School.objects.filter(name='avx').last() #如果没有这个值 这会返回 None
print (d,e) ---> None None
以字典的形式去取值
dict1 = {'name':'hc'}
ojb = models.School.object.filter(**dict1).first()
print (ojb) ---> None
dict1 = {'name':'你爸爸大学'}
ojb = models.School.object.filter(**dict1).first()
print (ojb) ---> 你爸爸大学
查询
查询相关API:
-
filter(**kwargs): 它包含了与所给筛选条件相匹配的对象
-
all(): 查询所有结果
-
get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。
-----------下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()--------
-
values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列 ;-->拿到库内 固定列内的 值作为 一对 key value ;如果条件是多个,那就拿到多对,整体是一个列表
-
exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象
-
order_by(*field): 对查询结果排序
-
reverse(): 对查询结果反向排序
-
distinct(): 从返回结果中剔除重复纪录
-
values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列;相比于 values 左后是一个 元祖,不是字典
-
count(): 返回数据库中匹配查询(QuerySet)的对象数量。
-
first(): 返回第一条记录
-
last(): 返回最后一条记录
-
exists(): 如果QuerySet包含数据,就返回True,否则返回False
双下划线(__)之单表条件查询
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in
models.Tb1.objects.filter(name__contains="ven")
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and
startswith,istartswith, endswith, iendswith,
增,改,删 (1,2)
create ,update, delete
增加 create
create方式一: Author.objects.create(name='Alvin')
create方式二: Author.objects.create(**{"name":"alex"})
save方式一: author=Author(name="alvin")
author.save()
save方式二: author=Author()
author.name="alvin"
author.save()
改的第一种方式 update ,推荐
第二种方式 通过get ,找不到会报错
需要先用get查,然后使用点,点出来再重新修改,最后save() 保存 .name()
obj_c = models.School.objects.cteate(name='hc') #name 是字段信息
print (obj_c)
obj_d = models.School.objects.filter(name='hc').delete() #先查询,再删
print (obj_d)
改的第一种方式 update ,推荐
obj_u = models.School.objects.filter(name='hc').updata(name='hc1') #先查询,再修改
print (obj_u) # 这里只会拿到修改的条数
改的第二种方式 ,通过get ,如果找不到会报错
obj_g = models.School.objects.get(name='hc') #先通过get找到对应的字段,如果找不到会报错
obj_g.name='hc2' # 用这个queryset对象再字段点出来 --> .name 再重新赋值给它,即为修改
obj_g.save() #最后用到这个 save() 方法将其保存写入到数据库
注意:
-
第二种方式修改不能用get的原因是:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合,比如name='alvin',可能有两个name='alvin'的行数据)。
-
在“插入和更新数据”小节中,我们有提到模型的save()方法,这个方法会更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列。
dic = {'name':'hc1'}
# 增加 'hc','hc1'
for i in ['hc','hc1']:
obj_c = models.School.objects.create(name=i)
#name 是字段信息,对应字段直接加数据
print (obj_c,type(obj_c))
# 删除 hc
obj_d = models.School.objects.filter(name='hc').delete()
#先查询,再删
print (obj_d,type(obj_d))
# 这里只会拿到删除的条数
# 更新 1 update --> hc2
obj_u = models.School.objects.filter(**dic).update(name='hc2')
#先查询,再修改
print (obj_u,type(obj_u)) # 这里只会拿到修改的条数
# 更新 2 get --> hc3
obj_g = models.School.objects.get(name='hc2')
#先通过get找到对应的字段,如果找不到会报错
obj_g.name = 'hc3'
# 用这个queryset对象再字段点出来 --> .name 再重新赋值给它,即为修改
obj_g.save()
#最后用到这个 save() 方法将其保存写入到数据库
print(obj_g,type(obj_g))
#输出
print (obj_c,type(obj_c))
hc <class 'hc.models.School'>
hc1 <class 'hc.models.School'>
print (obj_d,type(obj_d))
(1, {'hc.School': 1}) <class 'tuple'> #修改的条数
print (obj_u,type(obj_u))
1 <class 'int'> #修改的条数
print(obj_g,type(obj_g))
hc3 <class 'hc.models.School'>