web开发学习(2) - 从数据库迁移开始(bootcamp)

一. 内容背景:

在启动服务器前,需要创建数据库,进行数据库的迁移,通常执行以下命令; 本文主要从这条命令开始,顺藤摸瓜。

python manage.py migrate

二. 主要的线索:

1. 入口:./bootcamp/bootcamp/manage.py

#设置环境变量:让os.environ['DJANGO_SERRINGS_MODULE'] = ./config/settings/local.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')

current_path = os.path.dirname(os.path.abspath(__file__))  #获取当前manage.py文件的目录:./bootcamp/boocamp/
sys.path.append(os.path.join(current_path, 'bootcamp'))   #设置项目的路径:./bootcamp/bootcamp/bootcamp

execute_from_command_line(sys.argv)    #1. 获取命令行参数,执行函数,转

2. 执行execute_from_command_line(sys.argv) 

目录:./bootcamp/ll_env/lib/python3.7/site-packages/django/core/management/__init__.py

def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    utility = ManagementUtility(argv)
    utility.execute()

3. 执行execute()函数

目录:./bootcamp/ll_env/lib/python3.7/site-packages/django/core/management/__init__.py

class ManagementUtility:

    #def fetch_command(self, subcommand):
    #def autocomplete(self):

    def execute(self):
        try:
            subcommand = self.argv[1]   #获取命令行参数,此时subcommand = migrate
        except IndexError:
            subcommand = 'help'  # Display help if no arguments were given.
        #以下是检测命令行参数相关的代码
        parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False)
        parser.add_argument('--settings')    
        parser.add_argument('--pythonpath')
        parser.add_argument('args', nargs='*')  # catch-all
        try:
            options, args = parser.parse_known_args(self.argv[2:]) # 报异常,因为我们的命令行参数只有一个,python manage.py migrate;所以argv[2:]没有值
            handle_default_options(options)  #当使用了--setting/--pythonpath可选命令时执行
        except CommandError:
            pass  # Ignore any option errors at this point.

        try:
            settings.INSTALLED_APPS  #当调用用句点'.'访问的对象属性不存在时,将执行该对象的__getattr__()函数
        except ImproperlyConfigured as exc:
            self.settings_exception = exc
        except ImportError as exc:
            self.settings_exception = exc

4. 访问settings.INSTALLED_APPS(采用单例设计模式)

目录:./bootcamp/ll_env/lib/python3.7/site-packages/django/conf/__init__.py

目的:settings.INSTALLED_APPS属性的初始化

配置信息的流向:

- global_seting.py  ---》 settings._wraped

- local.py -- 》 settings._wraped

- settings._wraped --》settings.__dict__

#采用单例设计模式:让一个类在项目中只存在一个对象,即使用到这个类的地方很多,也只存在一个对象

settings = LazySettings()      # settings.INSTALLED_APPS , settings是LazySettings类的一个实例

LazySetting类

#当调用用句点'.'访问的对象属性不存在时,将执行该对象的__getattr__()函数

class LazySettings(LazyObject): 
    def _setup(self, name=None):
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)  #3. 取得第1步设置的环境变量的值:./config/settings/local.py
        if not settings_module:
            desc = ("setting %s" % name) if name else "settings"
            raise ImproperlyConfigured(
                "Requested %s, but settings are not configured. "
                "You must either define the environment variable %s "
                "or call settings.configure() before accessing settings."
                % (desc, ENVIRONMENT_VARIABLE))

        self._wrapped = Settings(settings_module)  #4. self._wrapped是Settings类的实例

    def __getattr__(self, name):       #1. settings.INSTALLED_APPS, name=INSTALLED_APPS
        """Return the value of a setting and cache it in self.__dict__."""
        if self._wrapped is empty:
            self._setup(name)          #2.从django/conf/global_settings.py文件获取相关配置信息,存于self._wrapped中(一个Settings类),其中就有INSTALLED_APPS
        val = getattr(self._wrapped, name)   #5. 想要获得Settings类实例的name属性(INSTALLED_APPS)
        self.__dict__[name] = val
        return val

5. val=getattr(self._wrapped, name) 

目录:./bootcamp/ll_env/lib/python3.7/site-packages/django/conf/__init__.py

想要获得Settings类实例的name属性,但是Settings类中找不到name=INSTALLED_APP的属性,也没有找到__getattr__方法属性,在Setting类初始化过程找找线索:setattr(self, setting, getattr(global_setting,setting))

语法:setattr(object,name,value)   等价于 set object.name=value

所以,self.setting=getattr(global_setting,setting),在global的配置文件中存在INSTALLED_APP,这样在回到第4步的val,

val=getattr(global_setting,setting), 相当于找到global_seting中的INSTALLED_APP的值,最后再返回到第3步

class Settings:
    def __init__(self, settings_module):    #setting_module is envpath
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))   # sytax: setattr(object,name,value)   euqal to set object.name=value
                #self.setting=getattr(global_setting,setting)   #获取global_setting的配置信息
        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module

        mod = importlib.import_module(self.SETTINGS_MODULE)     #import the moudle /bootcamp/config/settings/local.py

        tuple_settings = (
            "INSTALLED_APPS",
            "TEMPLATE_DIRS",
            "LOCALE_PATHS",
        )

        self._explicit_settings = set()     #create a no duplicate set(a kind of collections)
        for setting in dir(mod):

            if setting.isupper():
                setting_value = getattr(mod, setting)

                if (setting in tuple_settings and
                        not isinstance(setting_value, (list, tuple))):
                    raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting)
                setattr(self, setting, setting_value)    #用来自bootcamp/conf/settings/local.py文件的信息更新Setting类中的Setting中的配置信息
                self._explicit_settings.add(setting)

6. setting.INSTALLED_APPS里面有那些内容呢

目录:bootcamp/conf/settings/local.py

local.py文件的中INSTALLED_APPS的配置信息还使用了同文件夹下base.py的数据,可能还有其他的重要的配置信息,所以此时的settings.INSTALLED已经包含包含列很多信息了,只是我还不知道有什么用

#文件  bootcamp/conf/settings/local.py  部分代码省略
from .base import *  # noqa
INSTALLED_APPS += ['debug_toolbar']  # noqa F405
INSTALLED_APPS += ['django_extensions']  # noqa F405

#文件 bootcamp/conf/settings/base.py

# APPS
# ------------------------------------------------------------------------------
DJANGO_APPS = [
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.humanize',
    'django.contrib.admin',
    'django.forms',
]
THIRD_PARTY_APPS = [
    'crispy_forms',
    'sorl.thumbnail',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    # 'allauth.socialaccount.providers.amazon',
    # 'allauth.socialaccount.providers.github',
    # 'allauth.socialaccount.providers.google',
    # 'allauth.socialaccount.providers.linkedin',
    # 'allauth.socialaccount.providers.slack',
    'channels',
    'django_comments',
    'graphene_django',
    'markdownx',
    'taggit',
]
LOCAL_APPS = [
    'bootcamp.users.apps.UsersConfig',
    # Your stuff: custom apps go here
    'bootcamp.articles.apps.ArticlesConfig',
    'bootcamp.messager.apps.MessagerConfig',
    'bootcamp.news.apps.NewsConfig',
    'bootcamp.notifications.apps.NotificationsConfig',
    'bootcamp.qa.apps.QaConfig',
    'bootcamp.search.apps.SearchConfig'
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

 7. 执行django.setup (从第3步到这边)

目录:

(1) ./bootcamp/ll_env/lib/python3.7/site-packages/django/core/management/__init__.py

(2) django/__init__()     #这是django.setup()代码所在的文件

 # 文件./bootcamp/ll_env/lib/python3.7/site-packages/django/core/management/__init__.py

 if settings.configured:
            if subcommand == 'runserver' and '--noreload' not in self.argv:
                #此处代码省略......
            else:
                django.setup()    # 第一步

        self.autocomplete()      #第二步

        if subcommand == 'help':
            #次处代码省略......
        elif subcommand == 'version' or self.argv[1:] == ['--version']:
            sys.stdout.write(django.get_version() + '\n')
        elif self.argv[1:] in (['--help'], ['-h']):
            sys.stdout.write(self.main_help_text() + '\n')
        else:
            self.fetch_command(subcommand).run_from_argv(self.argv)   #第三步

 django.setup()  --》 apps.populate(settings.INSTALLED_APPS) 执行进行APPS的注册

#文件  django/__init__() 
def setup(set_prefix=True):

    configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
    if set_prefix:
        set_script_prefix(
            '/' if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME
        )
    apps.populate(settings.INSTALLED_APPS)

8. apps注册(也采用单例设计模式)

目录:django/apps/__init__.py    django/apps/config.py      django/apps/registry.py

#文件 django/__init__.py         注意这边的设置,有点奇怪,还是没有理解透彻,先跳过
from .config import AppConfig
from .registry import apps

__all__ = ['AppConfig', 'apps']

采用单例设计模式 

class Apps:
    #省略部分代码
    def __init__(self, installed_apps=()):
        #---省略代码-----
    def populate(self, installed_apps=None): 
        #---省略代码-----

apps = Apps(installed_apps=None)
实际代码部分

目的:

- 创建一个包含已注册app相关配置信息的一个实例对象,这些配置信息与installed_apps中的内容相对应

- 将以上的配置信息绑定到实例对象apps上面

问题:

从apps我们可以获得什么,如何获取?

app.config都包含哪些信息?

class Apps:
    #省略部分代码和注释
    def __init__(self, installed_apps=()):

        self.all_models = defaultdict(OrderedDict)    # self.all_models[key]=ordereddict
        self.app_configs = OrderedDict()
        self.stored_app_configs = []
        self.apps_ready = self.models_ready = self.ready = False
        self.ready_event = threading.Event()
        self._lock = threading.RLock()
        self.loading = False
        self._pending_operations = defaultdict(list)

        if installed_apps is not None:
            self.populate(installed_apps)

    def populate(self, installed_apps=None): 

        if self.ready:
            return

        with self._lock:   #进入这部分代码
            if self.ready:
                return

            for entry in installed_apps:      #entry = 'django_extensions'....
                if isinstance(entry, AppConfig):
                    app_config = entry
                else:
                    app_config = AppConfig.create(entry)    #创建一个包含已注册app相关配置信息的一个实例对象,这些配置信息与installed_apps中的内容相对应
                if app_config.label in self.app_configs:
                    raise ImproperlyConfigured(
                        "Application labels aren't unique, "
                        "duplicates: %s" % app_config.label)

                self.app_configs[app_config.label] = app_config   #将以上的配置信息绑定到实例对象apps上面
                app_config.apps = self       # 这一步为什么这么写呢????

            # Check for duplicate app names.
            counts = Counter(
                app_config.name for app_config in self.app_configs.values())
            duplicates = [
                name for name, count in counts.most_common() if count > 1]
            if duplicates:
                raise ImproperlyConfigured(
                    "Application names aren't unique, "
                    "duplicates: %s" % ", ".join(duplicates))

            self.apps_ready = True

            # Phase 2: import models modules.
            for app_config in self.app_configs.values():
                app_config.import_models()

            self.clear_cache()

            self.models_ready = True

            # Phase 3: run ready() methods of app configs.
            for app_config in self.get_app_configs():
                app_config.ready()

            self.ready = True
            self.ready_event.set()     #将Event的标志设置为True

9.  app_config = AppConfig.create(entry) 

目录: django/apps/config.py

AppConfig重点包含了哪些信息:包含本地app文件所在的目录(app_name),以及目录中一些可用的模块(app_module),这部分将绑定在了实例对象apps上面,让系统取用(通过单例设计模式,整个系统只有这一个对象)

class AppConfig:
    """Class representing a Django application and its configuration."""

    def __init__(self, app_name, app_module):  #app_name:实际是app所在的目录,app_modulue实际上是app所在目录下一些可以用的模块
        self.name = app_name
        self.module = app_module
        self.apps = None

        if not hasattr(self, 'label'):
            self.label = app_name.rpartition(".")[2]  #app_name=bootcamp.articles    self.labe=articles

        if not hasattr(self, 'verbose_name'):
            self.verbose_name = self.label.title()    #self.verbose_name=Articles,首字母大写

        if not hasattr(self, 'path'):
            self.path = self._path_from_module(app_module)

        self.models_module = None
        self.models = None

    @classmethod        #不用实例化对象就可使用的方法
    def create(cls, entry):     #entry = 'django_extensions'

        try:
            module = import_module(entry)   #将installed_apps的模块(app)导入进来,哪些模块:参考bootcamp/conf/settings/base.py
                                            #这边以'bootcamp.articles.apps.ArticlesConfig'为例
        except ImportError:
          #...代码省略....
        else:                                     #这部分代码块主要为了获得配置信息所在的目录,以及配置包含信息类的名字
            try:
                entry = module.default_app_config  #死活找不到 default_app_config属性是什么时候加进来的,猜测是创建app的时候,命令startapp,先跳过
            except AttributeError:
                # Otherwise, it simply uses the default app config class.
                return cls(entry, module)
            else:
                mod_path, _, cls_name = entry.rpartition('.')  #mod_path=bootcamp.articles.apps  cls_name=ArticlesConfig

        mod = import_module(mod_path)   #mod_path=bootcamp.articles.apps
        try:
            cls = getattr(mod, cls_name)   #获取到包含配置信息的类class ArticlesConfig
        except AttributeError:
            # ....代码省略......

        if not issubclass(cls, AppConfig):    #cls=class ArticlesConfig
            raise ImproperlyConfigured(
                "'%s' isn't a subclass of AppConfig." % entry)

        try:
            app_name = cls.name          #获取该app文件所在目录 app_name = bootcamp.articles
        except AttributeError:
            raise ImproperlyConfigured(
                "'%s' must supply a name attribute." % entry)

        # Ensure app_name points to a valid module.
        try:
            app_module = import_module(app_name)   #app_name = bootcamp.articles
        except ImportError:
            raise ImproperlyConfigured(
                "Cannot import '%s'. Check that '%s.%s.name' is correct." % (
                    app_name, mod_path, cls_name,
                )
            )

        # Entry is a path to an app config class.
        return cls(app_name, app_module)  #根据以上信息实例化一个AppConfigs实例对象,并返回该对象

 我的文件夹示例(以bootcamp项目下面的articles这个app为例) 

web开发学习(2) - 从数据库迁移开始(bootcamp)_第1张图片

 10. 现在要回到第3步,执行下面的命令(上面还有一个关注点是线程的控制,先跳过)

目录:django/core/management/__init__.py

#文件 django/core/management/__init__.py

self.fetch_command(subcommand).run_from_argv(self.argv)

执行fetch_command命令:

返回一个位于django/core/management/comands/migrate.py文件中的Command类的一个实例对象,然后调用run_from_argv(self.argv)

class ManagementUtility:
    def __init__(self, argv=None):
        #....代码省略....
    def autocomplete(self): 
        #....代码省略....
    def execute(self):
        #...部分代码省略....
        self.fetch_command(subcommand).run_from_argv(self.argv)
    def fetch_command(self, subcommand):           #跳到这里执行  subcommand=migrate
        """
        Try to fetch the given subcommand, printing a message with the
        appropriate command called from the command line (usually
        "django-admin" or "manage.py") if it can't be found.
        """
        # Get commands outside of try block to prevent swallowing exceptions
        commands = get_commands()     #返回django/core/management/commands目录的模块相关信息
                                      #commands = {'migrate':'django.core','runserver':'django.core','startapp':'django.core',.....}
        try:
            app_name = commands[subcommand]          #app_name='django.core'
        except KeyError:
            # ....代码省略....
        if isinstance(app_name, BaseCommand):
            # If the command is already loaded, use it directly.
            klass = app_name
        else:
            klass = load_command_class(app_name, subcommand)    #执行这里,#app_name='django.core'   subcommand='migrate'
                                                                #返回一个位于django/core/management/comands/migrate.py文件中的Command类的一个实例对象
        return klass

 11. Command类创建 和 run_from_argv(self.argv)方法调用       

目录:django/core/management/commands/migrate.py

Command继承自BaseCommand,它本身并没有run_from_argv(self.argv)方法,而是调用父类BaseCommand的方法

同时他也没有定义初始化方法__init__()

class Command(BaseCommand):
    #....部分代码省略.......
    def add_arguments(self, parser):
       #代码省略

    @no_translations
    def handle(self, *args, **options):
       #代码省略
----转到父类BaseCommand

目录:django/core/management/base.py 

注意:parser = self.create_parser(argv[0], argv[1])方法里面有一条语句,是调用子类的的方法self.add_arguments(parser):设置了可以命令--database的默认参数,当我们直接输入命令python manage.py migrate时(而不用指定--database database_argument),将采用默认设置

class BaseCommand:
    #定义了一些全局变量,看不懂,先跳过
    help = ''
    _called_from_command_line = False
    output_transaction = False  # Whether to wrap the output in a "BEGIN; COMMIT;"
    requires_migrations_checks = False
    requires_system_checks = True
    base_stealth_options = ('skip_checks', 'stderr', 'stdout')
    stealth_options = ()

    def __init__(self, stdout=None, stderr=None, no_color=False, force_color=False):
        #代码省略.....

    def create_parser(self, prog_name, subcommand, **kwargs):
        #代码省略.....

    def run_from_argv(self, argv):

        self._called_from_command_line = True
        parser = self.create_parser(argv[0], argv[1])   #注意该方法里面有一条语句,是调用子类的的方法self.add_arguments(parser):设置了可以命令--database的默认参数,当我们直接输入命令python manage.py migrate时(而不用指定--database database_argument),将采用默认设置

        options = parser.parse_args(argv[2:])   #没看懂,跳过

        cmd_options = vars(options)  #返回一个与命令行参数相关的字典,其中就有{'database':'default',.......}
        # Move positional args out of options to mimic legacy optparse
        args = cmd_options.pop('args', ())   #args=()
        handle_default_options(options)      #没有提供额外的可选参数和命令,这里无影响,跳过

        try:
            self.execute(*args, **cmd_options)   #最后到这里
        except Exception as e:
            #代码省略......
        finally:
            try:
                connections.close_all()
            except ImproperlyConfigured:
                #代码省略......

12. 执行父类BaseCommand的execute(*args, **cmd_options)

目录:django/core/management/base.py 

有一点疑问:为什么要在子类和父类之间跳来跳去呢,不懂,先跳过 

class BaseCommand:
  #定义了一些全局变量,看不懂,先跳过
  help = ''
  _called_from_command_line = False
  output_transaction = False  # Whether to wrap the output in a "BEGIN; COMMIT;"
  requires_migrations_checks = False
  requires_system_checks = True
  base_stealth_options = ('skip_checks', 'stderr', 'stdout')
  stealth_options = ()

  def __init__(self, stdout=None, stderr=None, no_color=False, force_color=False):   #代码省略.....

  def create_parser(self, prog_name, subcommand, **kwargs):    #代码省略.....

  def run_from_argv(self, argv):  #代码省略.....

  def execute(self, *args, **options):
    #.....省略部分代码..............
    if self.requires_system_checks and not options.get('skip_checks'):
        self.check()                         #跳过
    if self.requires_migrations_checks:
        self.check_migrations()              #跳过

    output = self.handle(*args, **options)   #将调用子类Command.handle()方法,其中args={}, options={'database':'default',......}
                                             #跳转到子类Command,目录:django/core/management/commands/migrate.py
    if output:
        if self.output_transaction:
            connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
            output = '%s\n%s\n%s' % (
                self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()),
                output,
                self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),
            )
        self.stdout.write(output)
    return output

--跳转到子类Commmd.handle()方法

目录:django/core/management/commands/migrate.py

class Command(BaseCommand):
    def add_arguments(self, parser):
        #...部分代码省略....
        parser.add_argument(
            '--database',
            default=DEFAULT_DB_ALIAS,             #该条重要,参考第11步,本步骤于此无关
            help='Nominates a database to synchronize. Defaults to the "default" database.',
        )

    @no_translations
    def handle(self, *args, **options):

        self.verbosity = options['verbosity']        #跳过
        self.interactive = options['interactive']    #跳过

        for app_config in apps.get_app_configs():
            if module_has_submodule(app_config.module, "management"):    #确认INSTALLED_APPS中app文件目录下是否还有management这个包,如果有的话,就导入这个包
                import_module('.management', app_config.name) #如果app_config_name=bootcamp/articles, 本句相当于:import bootcamp/articles/management, 但对于articles这个app,management是不存在的

        # Get the database we're operating from
        db = options['database']        #db='default'  参考第11步 create_parser()
        connection = connections[db]    #最后,跳转到这里
  
        #准备数据库时,后端backend所需要的Hook 
        connection.prepare_database()   #跳过:django/db/base/base.py
        #.......后续代码省略.......

13. 获取数据库connection = connections[db]

 目录:django/db/__init__.py

 首先这又是一个单例模式,跳转到ConectionHandler() ,目录:django/db/utils.py

from django.core import signals
from django.db.utils import (
    DEFAULT_DB_ALIAS, DJANGO_VERSION_PICKLE_KEY, ConnectionHandler,
    ConnectionRouter, DatabaseError, DataError, Error, IntegrityError,
    InterfaceError, InternalError, NotSupportedError, OperationalError,
    ProgrammingError,
)

__all__ = [
    'connection', 'connections', 'router', 'DatabaseError', 'IntegrityError',
    'InternalError', 'ProgrammingError', 'DataError', 'NotSupportedError',
    'Error', 'InterfaceError', 'OperationalError', 'DEFAULT_DB_ALIAS',
    'DJANGO_VERSION_PICKLE_KEY',
]

connections = ConnectionHandler()

--跳转到ConnectionHandler

目录:django/db/utils.py

划重点:Threading.local()方法,多线程局部变量,

我的初步理解:多个线程共同使用一个变量名字,但是实际每个线程内部访问该变量数据时,是相互独立的,不会相互干扰

class ConnectionHandler:
    #--省略部分代码--
    def __init__(self, databases=None):
        self._databases = databases
        self._connections = local()    #多线程局部变量!!!!!

    def __getitem__(self, alias): #alias='default' 
        if hasattr(self._connections, alias): 
            return getattr(self._connections, alias)  #在第12步调用Connections[db],跳转到此处 alias='default' 
                                                      #类刚初始化后,self._connections为空,条件不符合,跳到下一步
        self.ensure_defaults(alias)         #设置一些默认的键值对,在self._database里面
        self.prepare_test_settings(alias)   #测试作用吧,跳过
        db = self.databases[alias]          #db={env.db('DATABASE_URL'),'NAME':'','USER':'','PASSWORD':'','HOST':'','PORT':'',.....,'ATOMIC_REQUESTS':False,'AUTOCOMMIT':True}
        backend = load_backend(db['ENGINE'])  #相当于import django.db.backends.postgresql.base
        conn = backend.DatabaseWrapper(db, alias)     #最后跳转到这一步
        setattr(self._connections, alias, conn)
        return conn

    @cached_property
    def databases(self):
        if self._databases is None:
            self._databases = settings.DATABASES   #self._databases={ 'default': env.db('DATABASE_URL'),}   bootcamp/config/settings/base.py
        if self._databases == {}:
            self._databases = {
                DEFAULT_DB_ALIAS: {
                    'ENGINE': 'django.db.backends.dummy',
                },
            }
        if DEFAULT_DB_ALIAS not in self._databases:
            raise ImproperlyConfigured("You must define a '%s' database." % DEFAULT_DB_ALIAS)
        if self._databases[DEFAULT_DB_ALIAS] == {}:
            self._databases[DEFAULT_DB_ALIAS]['ENGINE'] = 'django.db.backends.dummy'
        return self._databases                     #返回值 { 'default': env.db('DATABASE_URL'),} 

14. conn=backend.DatabaseWrapper(db,alias)   

目录:django/db/postgresql/base.py

DatabaseWrapper是BaseDatabaseWrapper的子类,且未定义初始化方法__init__()

class DatabaseWrapper(BaseDatabaseWrapper): # ....代码省略....

 --跳转至父类BaseDatabaseWrapper,对象实例化

目录:django/db/backends/base/base.py

 看不懂,先跳过

class BaseDatabaseWrapper:
    #全局变量的定义,看不懂有什么用,先跳过
    """Represent a database connection."""
    # Mapping of Field objects to their column types.
    data_types = {}
    # Mapping of Field objects to their SQL suffix such as AUTOINCREMENT.
    data_types_suffix = {}
    # Mapping of Field objects to their SQL for CHECK constraints.
    data_type_check_constraints = {}
    ops = None
    vendor = 'unknown'
    display_name = 'unknown'
    SchemaEditorClass = None
    # Classes instantiated in __init__().
    client_class = None
    creation_class = None
    features_class = None
    introspection_class = None
    ops_class = None
    validation_class = BaseDatabaseValidation

    queries_limit = 9000

    def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS)   #初始化位置,settings_dict=db={'NAME': 'bootcamp', 'USER': 'u_bootcamp', 'PASSWORD': 'p4ssw0rd', 'HOST': 'localhost', 'PORT': 5432, 'ENGINE': 'django.db.backends.postgresql', 'ATOMIC_REQUESTS': True, 'AUTOCOMMIT': True, 'CONN_MAX_AGE': 0, 'OPTIONS': {}, 'TIME_ZONE': None, 'TEST': {'CHARSET': None, 'COLLATION': None, 'NAME': None, 'MIRROR': None}}

        # Connection related attributes.
        # The underlying database connection.
        self.connection = None
        # `settings_dict` should be a dictionary containing keys such as
        # NAME, USER, etc. It's called `settings_dict` instead of `settings`
        # to disambiguate it from Django settings modules.
        self.settings_dict = settings_dict
        self.alias = alias
        # Query logging in debug mode or when explicitly enabled.
        self.queries_log = deque(maxlen=self.queries_limit)
        self.force_debug_cursor = False

        # Transaction related attributes.
        # Tracks if the connection is in autocommit mode. Per PEP 249, by
        # default, it isn't.
        self.autocommit = False
        # Tracks if the connection is in a transaction managed by 'atomic'.
        self.in_atomic_block = False
        # Increment to generate unique savepoint ids.
        self.savepoint_state = 0
        # List of savepoints created by 'atomic'.
        self.savepoint_ids = []
        # Tracks if the outermost 'atomic' block should commit on exit,
        # ie. if autocommit was active on entry.
        self.commit_on_exit = True
        # Tracks if the transaction should be rolled back to the next
        # available savepoint because of an exception in an inner block.
        self.needs_rollback = False

        # Connection termination related attributes.
        self.close_at = None
        self.closed_in_transaction = False
        self.errors_occurred = False

        # Thread-safety related attributes.
        self._thread_sharing_lock = threading.Lock()
        self._thread_sharing_count = 0
        self._thread_ident = _thread.get_ident()

        # A list of no-argument functions to run when the transaction commits.
        # Each entry is an (sids, func) tuple, where sids is a set of the
        # active savepoint IDs when this function was registered.
        self.run_on_commit = []

        # Should we run the on-commit hooks the next time set_autocommit(True)
        # is called?
        self.run_commit_hooks_on_set_autocommit_on = False

        # A stack of wrappers to be invoked around execute()/executemany()
        # calls. Each entry is a function taking five arguments: execute, sql,
        # params, many, and context. It's the function's responsibility to
        # call execute(sql, params, many, context).
        self.execute_wrappers = []

        self.client = self.client_class(self)
        self.creation = self.creation_class(self)
        self.features = self.features_class(self)
        self.introspection = self.introspection_class(self)
        self.ops = self.ops_class(self)
        self.validation = self.validation_class(self)

15 . 回到第十三步:setattr(self._connections, alias, conn)

目录:django/db/__init__.py

目的:将上一步获得的数据库连接的对象conn绑定到Connections的实例对象上,可返回步骤12/13查看,由于采用了单例设计模式,系统中只有这一个实例对象,还是不是很懂,这一部分,特别是结合Threading.local()多线程局部变量,后续再回来~

 

16. 再返回到第12步,继续执行(待续)

--目录django/core/management/commands/migrate.py

self.migration_progress_callback是一个方法,代码将一个方法传递给了一个类,该方法也位于当前目录中

class Command(BaseCommand):
    #-----部分代码省略-----------
    @no_translations
    def handle(self, *args, **options):
        db = options['database']      
        connection = connections[db]   #单例设计模式,指向一个数据库连接的对象,django/db/postgresql/base.py         
        connection.prepare_database()  #跳过

        # Work out which apps have migrations and which do not
        executor = MigrationExecutor(connection, self.migration_progress_callback)  #self.migration_progress_callback是一个方法,代码将一个方法传递给了一个类

        # Raise an error if any migrations are applied before their dependencies.
        executor.loader.check_consistent_history(connection)

--目录:django/db/migration/executor   

class MigrationExecutor:     #这可能是一个具有数据库迁移功能的类
    def __init__(self, connection, progress_callback=None):
        self.connection = connection        #获取数据的连接对象
        self.loader = MigrationLoader(self.connection)
        self.recorder = MigrationRecorder(self.connection)
        self.progress_callback = progress_callback

--目录:django/db/migration/loader

MigrationLoader类的作用是:从磁盘加载迁移文件,并且从数据库加载迁移文件的状态

在app的目录下,有一个migration的文件的夹,在类初始化时,通常会扫描该文件夹,搜索一个叫Migration的类,该类继承自django.db.migrations.Migration

class MigrationLoader:
    def __init__(self, connection, load=True, ignore_no_migrations=False):
        self.connection = connection
        self.disk_migrations = None
        self.applied_migrations = None
        self.ignore_no_migrations = ignore_no_migrations
        if load:
            self.build_graph()

--目录:django/db/migration/recorder

MigrationRecorder类主要负责将迁移的记录存储到数据库中

class MigrationRecorder:
    _migration_class = None
    def __init__(self, connection):
        self.connection = connection

完成各迁移相关类的初始化后,执行命令:executor.loader.check_consistent_history(connection)

check_consistent_history()方法位于目录:django/db/migration/loader(MigrationLoader类内部)

class MigrationLoader:
    def __init__(self, connection, load=True, ignore_no_migrations=False):
        self.connection = connection
        self.disk_migrations = None
        self.applied_migrations = None
        self.ignore_no_migrations = ignore_no_migrations
        if load:
           self.build_graph()

    def check_consistent_history(self, connection):
        recorder = MigrationRecorder(connection)     #获取该连接相关的迁移记录
        applied = recorder.applied_migrations()      #跳到这里
        #部分代码省略---

执行:applied = recorder.applied_migration()

目录:django/db/migration/recorder

def applied_migrations(self):
    if self.has_table():
        return {tuple(x) for x in self.migration_qs.values_list('app', 'name')}   #运行这里
    else:
        return set()

@property
def migration_qs(self):
    return self.Migration.objects.using(self.connection.alias)   在运行这里,为什么找不到objects啊????

首先Migration是类对象,通过元类BaseMigration创建对象,他同时是Model的子类,Model类对象也通过元类BaseMigration创建

  • _meta什么时候加入的?
  • self.Migration.objects哪里找?

目录:django/db/migrations/models/base.py

class ModelBase(type):
    """Metaclass for all models."""
    def __new__(cls, name, bases, attrs, **kwargs):
        super_new = super().__new__     #super_new=type.__new__

        # Also ensure initialization is only performed for subclasses of Model
        # (excluding Model class itself).
        parents = [b for b in bases if isinstance(b, ModelBase)]    #for class Migration, parents = Model
                                                                    #for class Model, parents = []

        if not parents:
            return super_new(cls, name, bases, attrs)

        # Create the class. 部分代码省略
        new_class = super_new(cls, name, bases, new_attrs, **kwargs)   #最后将返回这个类对象,也就是Migration
        #----部分代码省略----
        new_class.add_to_class('_meta', Options(meta, app_label))  #add_to_class方法在元类BaseModel里面定义,_meta在这里被引入到Mmigration中
        #----部分代码省略----

        new_class._prepare()
        new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
        return new_class        #new_class就是最后创建的Migration类对象
#django/db/migrations/models/base.py
def _prepare(cls):
    """Create some methods once self._meta has been populated."""
    opts = cls._meta          #cls=new_class
    opts._prepare(cls)        #这步是把一个autoField的类绑定到我们的Migration类对象上
                              #django/models/options.py 

    if not opts.managers:    #opts.managers=None
        if any(f.name == 'objects' for f in opts.fields):
            raise ValueError(‘此处代码省略’)
        manager = Manager()                       #django/db/migrations/models/manager.py !!!!!!!!!
        manager.auto_created = True
        cls.add_to_class('objects', manager)      #objects就是在这里引入啊!!!!!!!!!!,他指向了manager类

    class_prepared.send(sender=cls)        #没看懂,先跳过

17. Manager对象

#文件 django/db/models/manager.py
class Manager(BaseManager.from_queryset(QuerySet)):    #class Manager(BaseManagerFromQuerySet)
    pass

#文件 django/db/models/manager.py
class BaseManager:
    #......部分代码省略.....
    @classmethod
    def from_queryset(cls, queryset_class, class_name=None):
        if class_name is None:
            class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)  #class_name = BaseManagerFromQuerySet
        return type(class_name, (cls,), {          #type创建类的语法:type(name,bases,attrs)                      
           
            '_queryset_class': queryset_class,     #创建一个BaseManagerFromQuerySet类,该类继承BaseManager,并绑定QuerySet类
            **cls._get_queryset_methods(queryset_class), #没看懂,可能是绑定一些方法
        })

#文件 django/db/models/query.py 
class QuerySet:
    """Represent a lazy database lookup for a set of objects."""

    def __init__(self, model=None, query=None, using=None, hints=None):
        self.model = model
        self._db = using
        self._hints = hints or {}
        self.query = query or sql.Query(self.model)
        self._result_cache = None
        self._sticky_filter = False
        self._for_write = False
        self._prefetch_related_lookups = ()
        self._prefetch_done = False
        self._known_related_objects = {}  # {rel_field: {pk: rel_obj}}
        self._iterable_class = ModelIterable
        self._fields = None

    def _chain(self, **kwargs):
        """
        Return a copy of the current QuerySet that's ready for another
        operation.
        """
        obj = self._clone()
        if obj._sticky_filter:
            obj.query.filter_is_sticky = True
            obj._sticky_filter = False
        obj.__dict__.update(kwargs)
        return obj

    def _clone(self):
        """
        Return a copy of the current QuerySet. A lightweight alternative
        to deepcopy().
        """
        c = self.__class__(model=self.model, query=self.query.chain(), using=self._db, hints=self._hints)
        c._sticky_filter = self._sticky_filter
        c._for_write = self._for_write
        c._prefetch_related_lookups = self._prefetch_related_lookups[:]
        c._known_related_objects = self._known_related_objects
        c._iterable_class = self._iterable_class
        c._fields = self._fields
        return c

    def using(self, alias):
        """Select which database this QuerySet should execute against."""
        clone = self._chain()
        clone._db = alias
        return clone

18. self.Migration.objects.using(self.connection.alias)

目录:django/db/migration/recorder

 获取QuerySet的一份拷贝,其中_db值用self.connection.alias替换

#文件 django/db/migration/recorder
@property
def migration_qs(self):                    
    return self.Migration.objects.using(self.connection.alias)    #获取QuerySet的一份拷贝,其中_db值用self.connection.alias替换

def applied_migrations(self):
    """Return a set of (app, name) of applied migrations."""
    if self.has_table():
        return {tuple(x) for x in self.migration_qs.values_list('app', 'name')}  #values_list是QuerySet的方法
    else:
        # If the django_migrations table doesn't exist, then no migrations
        # are applied.
        return set()

 

只能到这了,搞不动。。。。。。。。。。。。。。。。。。。

 

你可能感兴趣的:(web开发)