Horizon 源码阅读(三)—— Horizon 用户登录流程

一、写在前面

这篇文章主要介绍一下OpenStack Horizon — juno项目用户登录流程,Horizon对于请求的处理其实就是django对请求的处理,我在这里将通过对django请求处理机制以及horizon用户登录流程代码的解析,逐步开始horizon代码阅读之旅。由于能力和时间有限,错误之处在所难免,欢迎指正!

如果转载,请保留作者信息。

邮箱地址:jpzhang.ht@gmail.com

二、Django  请求处理流程

当你通过在浏览器里敲http://127.0.0.1:8000/hello/来访问Hello world消息的时候,Django在后台有些什么动作呢?

流程图:

 

所有均开始于setting文件。当你运行Python manage.py runserver,脚本将在于manage.py同一个目录下查找名为setting.py的文件。这个文件包含了所有有关这个Django项目的配置信息,均大写: TEMPLATE_DIRS , DATABASE_NAME , 等. 最重要的设置时ROOT_URLCONF,它将作为URLconf告诉Django在这个站点中那些Python的模块将被用到

还记得什么时候django-admin.py startproject创建文件settings.py和urls.py吗?自动创建的settings.py包含一个ROOT_URLCONF配置用 来指向自动产生的urls.py. 打开文件settings.py你将看到如下:

[python]  view plain  copy
  1. ROOT_URLCONF = 'mysite.urls'  

相对应的文件是mysite/urls.py

当访问 URL /hello/ 时,Django 根据 ROOT_URLCONF 的设置装载 URLconf 。 然后按顺序逐个匹配URLconf里的URLpatterns,直到找到一个匹配的。 当找到这个匹配 的URLpatterns就调用相关联的view函数,并把 HttpRequest 对象作为第一个参数。 

总结一下:

进来的请求转入/hello/.

Django通过在ROOT_URLCONF配置来决定根URLconf.

Django在URLconf中的所有URL模式中,查找第一个匹配/hello/的条目。

如果找到匹配,将调用相应的视图函数

视图函数返回一个HttpResponse

Django转换HttpResponse为一个适合的HTTP response, 以Web page显示出来

三、Horizon用户登录流程

       说明:在具体分析horizon用户注册源码,会涉及到Horizon Template模版加载机制,本篇博文不具体说明,关于Template内容放到下个专题,专门一篇博文进行说明。

       言归正传首先horizon本身基于Django实现,简单地说就是网站的架构,主要分析前端请求和后端处理。所以第一步就是用户发起访问请求,第二步是找到前端请求与后端处理的绑定,具体为此次动作由哪个函数来处理等等;第三步,horizon必然会调用openstack Keystone的API接口验证访问用户,这里只分析后端处理流程,止步于API调用,暂且不详细分析。

       根据Django的框架结构,使用URLconf文件(https://docs.djangoproject.com/en/1.4/topics/http/urls/)进行链接请求和后端处理(view)的绑定,使用view进行后端处理,使用template进行页面渲染。 前端请求与后端处理URL映射绑定是有urls.py完成,Django对URL设置内容是放在urls.py文件内,项目urls.py指定是在项目的setting.py配置文件内,那么OpenStack Horizon项目在setting.py中 ROOT_URLCONF、 TEMPLATE_DIRS 配置如下:

代码路径:/usr/share/openstack-dashboard/openstack_dashboard/setting.py

[python]  view plain  copy
  1. ROOT_URLCONF = ‘openstack_dashboard.urls’#来指向Horizon的urls.py  
  2. ……  
  3. #加载的Template模版  
  4. TEMPLATE_LOADERS = (  
  5.     'django.template.loaders.filesystem.Loader',  
  6.     'django.template.loaders.app_directories.Loader',  
  7.     'horizon.loaders.TemplateLoader'  
  8. )  
  9. TEMPLATE_DIRS = (  
  10.     os.path.join(ROOT_PATH, 'templates'),  
  11. )  

openstack_dashboard.urls.py构成了整个horizon项目的前端请求与后端处理的映射,那么Horizon 是如何实现整个Dashboard用户登录的呢?


Horizon用户登录数据流图: 



用户发起访问Request请求,用户访问Horizon地址: http://172.16.17.63/  

URL绑定:

[python]  view plain  copy
  1. """ 
  2. patterns() 函数并将返回结果保存到 urlpatterns 变量。patterns函数当前只有一个参数—一个空的字符串。 
  3. 这个字符串可以被用来表示一个视图函数的通用前缀。 
  4. 当前应该注意是 urlpatterns 变量, Django 期望能从setting.py->ROOT_URLCONF模块中找到它。 
  5. 该变量定义了 URL 以及用于处理这些 URL 的代码之间的映射关系。 
  6. """  
  7. urlpatterns = patterns(  
  8.     '',  
  9.     # 匹配网站根目录的URL,映射到openstack_dashboard.views.splash视图  
  10.     url(r'^$''openstack_dashboard.views.splash', name='splash'),  
  11.     # 任何以/auth/开头的URL将会匹配,引入openstack_auth.urls  
  12.     url(r'^auth/', include('openstack_auth.urls')),  
  13.   
  14.     # 任何以/api/开头的URL将会匹配,引入openstack_dashboard.api.rest.urls  
  15.     url(r'^api/', include('openstack_dashboard.api.rest.urls')),  
  16.   
  17.     # 任何访问URL将会匹配,都引用horizon.urls  
  18.     url(r'', include(horizon.urls)),  
  19. )  

后端处理:

根据前端请求的URL地址配置“url(r'^$', 'openstack_dashboard.views.splash', name=‘splash’),”由此得知后端处理函数:openstack_dashboard.views.splash()

地址:/usr/share/openstack-dashboard/openstack_dashboard/views.py

[python]  view plain  copy
  1. """ 
  2. splash()视图函数来处理url(r'^$', 'openstack_dashboard.views.splash', name='splash')。 
  3. 判断当前访问用户是否登录,如果登录跳转到该用户权限范围内的默认页面,如果没有登录打开登录界面。 
  4. """  
  5. @django.views.decorators.vary.vary_on_cookie  
  6. def splash(request):  
  7.     # is_authenticated() 登录用户是否通过验证,也就是通过用户名和密码判断该用户是否存在  
  8.     if request.user.is_authenticated():  
  9.         # shortcuts.redirect():返回一个HTTP响应,重定向到适当的URL参数。  
  10.         # 当前登录用户存在,shortcuts.redirect()重定向到horizon.get_user_home(request.user)返回的地址。  
  11.         # 假设我们这边访问的用户是"admin"管理员  
  12.         # horizon.get_user_home(request.user):"/project/"  
  13.         response = shortcuts.redirect(horizon.get_user_home(request.user))  
  14.     else:  
  15.         # 当前登录用户不存在或者用户没有登录,加载登录表单,跳转到登录页面  
  16.         # openstack_auth.forms.Login()创建Login登录form表单。  
  17.         # openstack_auth 身份验证是一个可插拔Django认证后端,  
  18.         # 使用Django的"contrib.auth"框架来调用OpenStack's Keystone的认证API来验证用户。  
  19.         form = forms.Login(request)  
  20.         # shortcuts.render() 返回一个HttpResponse的内容,  
  21.         # 'auth/login.html':登录表单模板文件  
  22.         # 'form':传递form表单参数  
  23.         response = shortcuts.render(request, 'auth/login.html', {'form': form})  
  24.     if 'logout_reason' in request.COOKIES:  
  25.         response.delete_cookie('logout_reason')  
  26.     return response  

splash()根据用户Request请求的不同类型提供了两种处理方式,一种:用户访问Horizon Web管理平台的地址,当前并没有用户登录,splash()函数加载登录界面;第二种:当前的请求带上了用户(登录),splash()函数验证用户的合法性,重定向到内部调用处理返回的用户访问地址。

这里分别进行两种请求的分析:


第一种:用户访问Horizon

生成form登录表单: splash()-> form = forms.Login(request)返回horizon登录表单。

from openstack_auth import forms引入form表单

openstack_auth:OpenStack是一个可插拔的Django项目,openstack_auth用户认证后端与Django contrib.auth框架来验证,提供OpenStack Keystone身份用户API。


[python]  view plain  copy
  1. class Login(django_auth_forms.AuthenticationForm):  
  2.   
  3.     """继承自django.contrib.auth.forms.AuthenticationForm:基类用户进行身份验证。扩展此得到接受用户名/密码登录形式。用于登录用户的表单  
  4. 通过提供域名,用户名和密码,处理Keystone认证。如果用户认证都有一个默认的项目组,该令牌将被自动作用于它们的默认项目。用户认证没有默认设置的项目,该认证后端将尝试范围从用户的指定项目返回的项目。范围内的第一个成功的项目将被退回。继承了基类“django.contrib.auth.forms.AuthenticationForm”以增加安全功能。  
  5.     “""  
  6.   
  7.     # 登录表单上面的region下拉选择框,不是必填项  
  8.     region = forms.ChoiceField(label=_("Region"), required=False)  
  9.   
  10.     # 登录表单上的用户名文本框,自动获取文本框焦点  
  11.     username = forms.CharField(  
  12.         label=_("User Name"),  
  13.         widget=forms.TextInput(attrs={"autofocus""autofocus"}))  
  14.   
  15.     # 登录表单上的密码框  
  16.     password = forms.CharField(label=_("Password"),  
  17.                                widget=forms.PasswordInput(render_value=False))  
  18.   
  19.     def __init__(self, *args, **kwargs):  
  20.         super(Login, self).__init__(*args, **kwargs)  
  21.         ''''' 
  22.         如果self是一种表单,它的字段是self.fields,这是一个django.utils.datastructures.SortedDict(它呈现在它们被添加的顺序的项目)。 
  23.         经过结构形式self.fields有keyOrder属性,它是包含在它们应该出现的顺序中的字段名称列表。 
  24.         你可以将其设置为正确顺序(虽然你需要小心,以确保您不会遗漏项目或增加额外)。 
  25.         '''  
  26.         # 设置在form表单控件出现的顺序  
  27.         self.fields.keyOrder = ['username''password''region']  
  28.   
  29.         if getattr(settings,  
  30.                    'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT',  
  31.                    False):  
  32.             # 读取local_setting.py OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT, True:OpenStack的KEYSTONE多域支持, False :不支持  
  33.             # Domain:域 不同与 region:区域  
  34.             # 登录表单上域的文本框。必填项  
  35.             self.fields['domain'] = forms.CharField(  
  36.                 label=_("Domain"),  
  37.                 required=True,  
  38.                 #  设置域自动回去焦点  
  39.                 widget=forms.TextInput(attrs={"autofocus""autofocus"}))  
  40.             # 取消用户名自动获取焦点  
  41.             self.fields['username'].widget = forms.widgets.TextInput()  
  42.             # 设置控件在表单中出现的顺序  
  43.             self.fields.keyOrder = ['domain''username''password''region']  
  44.   
  45.         # 获取多区域下拉选项值  
  46.         self.fields['region'].choices = self.get_region_choices()  
  47.   
  48.         if len(self.fields['region'].choices) == 1:  
  49.             # regions的长度为1即是默认值"Default Region"  
  50.             # 初始参数让你指定在一个未绑定的表单呈现该字段时要使用的初始值。  
  51.             self.fields['region'].initial = self.fields['region'].choices[0][0]  
  52.             # 设置region 控件为一个隐藏框  
  53.             self.fields['region'].widget = forms.widgets.HiddenInput()  
  54.         elif len(self.fields['region'].choices) > 1:  
  55.             # 如果长度大于1即horizon配置了多区域,初始化值为本地COOkIES的login_region值  
  56.             self.fields['region'].initial = self.request.COOKIES.get(  
  57.                 'login_region')  
  58.  
  59.     @staticmethod  
  60.     def get_region_choices():  
  61.         # settings.OPENSTACK_KEYSTONE_URL 获取多区域下拉选项值  
  62.         default_region = (settings.OPENSTACK_KEYSTONE_URL, "Default Region")  
  63.         # settings.AVAILABLE_REGIONS 可用区域的endpoint 和 标题  
  64.         regions = getattr(settings, 'AVAILABLE_REGIONS', [])  
  65.   
  66.         if not regions:  
  67.             # regions 设置默认值:“Default Region”  
  68.             regions = [default_region]  
  69.         return regions  

r

esponse = shortcuts.render(request, 'auth/login.html', {'form': form}):渲染auth/login.html表单模版,参数传递登录表单。

Template 模版路径:horizon/templates/login.html

<!-- 继承自horizon/templates/base.html-->

{% extends "base.html" %}

[python]  view plain  copy
  1. {% load i18n %}  
  2.   
  3. {% block title %}{% trans "Login" %}{% endblock %}  
  4.   
  5. {% block body_id %}splash{% endblock %}  
  6.   
  7. {% block content %}  
  8.   <!-- 导入horizon/templates/auth/_login.html-->  
  9.   {% include 'auth/_login.html' %}  
  10. {% endblock %}  

最终把response返回给客户端,展示登陆页面。


第二种:用户登录Horizon

splash()函数判断当前访问用户是否登录,如果登录跳转到该用户权限范围内的默认页面:

[python]  view plain  copy
  1. if request.user.is_authenticated():  
  2.       response=  
  3.       shortcuts.redirect(horizon.get_user_home(request.user))  

request.user.is_authenticated():True,调用horizon.get_uesr_home()

地址:horizon.base.Size:get_user_home(self, user)

[python]  view plain  copy
  1. def get_user_home(self, user):  
  2.     """返回一个特定用户的URL 
  3.        这个方法一个功能是可以定制用户登录界面,默认返回值:get_absolute_url() 
  4.     另一个功能是可以提供自定义行为通过指定一个URL或一个函数返回一个URL,通过在setting.py文件中定义HORIZON_CONFIG的参数值 
  5.     例如:setting.py -> HORIZON_CONFIG 
  6.     HORIZON_CONFIG = { 
  7.                         'user_home': 'openstack_dashboard.views.get_user_home', 
  8.                         'ajax_queue_limit': 10, 
  9.                         'auto_fade_alerts': { 
  10.                             'delay': 3000, 
  11.                             'fade_duration': 1500, 
  12.                             'types': ['alert-success', 'alert-info'] 
  13.                         }, 
  14.                         'help_url': "http://docs.openstack.org", 
  15.                         'exceptions': {'recoverable': exceptions.RECOVERABLE, 
  16.                                        'not_found': exceptions.NOT_FOUND, 
  17.                                        'unauthorized': exceptions.UNAUTHORIZED}, 
  18.                         'angular_modules': [], 
  19.                         'js_files': [], 
  20.     } 
  21.  
  22.     这可能是有用的如果默认的仪表板可能不是所有用户都可以访问。 
  23.     当HORIZON_CONFIG 中缺少user_home定义时默认返回settings.LOGIN_REDIRECT_URL值 
  24.     """  
  25.     # 调用_conf()函数 返回setting.py 中 HORIZON_CONFIG参数中的user_home值  
  26.     # admin 用户返回:'openstack_dashboard.views.get_user_home'  
  27.     user_home = self._conf['user_home']  
  28.     if user_home:  
  29.         # user_home有值  
  30.         if callable(user_home):# 检查对象user_home是否可调用。  
  31.             # 因为user_home值是一个字符串不可调用,没有进这个语句块。  
  32.             return user_home(user)  
  33.         elif isinstance(user_home, basestring):# 判断一个user_home是否为str或者unicode的实例  
  34.             # 因为user_home是一个str类型的字符串,进这个语句块  
  35.             # 假设我们的url即user_home值里面有“/”  
  36.             if '/' in user_home:# user_home 值没有"/"没有进这个语句块  
  37.                 return user_home  
  38.             else:  
  39.                 # 用"."分割user_home值  
  40.                 mod, func = user_home.rsplit("."1)  
  41.                 # mod:'openstack_dashboard.views'  
  42.                 # func:'get_user_home'  
  43.                 # user:<SimpleLazyObject: <User: admin>>  
  44.                 # import mod 调用func方法,传入参数user  
  45.                 # 执行openstack_dashboard.views.get_user_home()  
  46.                 return getattr(import_module(mod), func)(user)  
  47.        # user_home 值如果既不可调用,也不是字符串,认为这是错误的,报一个异常。  
  48.         raise ValueError('The user_home setting must be either a string '  
  49.                          'or a callable object (e.g. a function).')  
  50.     else:  
  51.         return self.get_absolute_url()  

setting.py文件定义了user_home参数值:

[python]  view plain  copy
  1. HORIZON_CONFIG = {  
  2.     'user_home''openstack_dashboard.views.get_user_home',  

加载openstack_dashboard.views 执行 get_user_home()

return getattr(import_module(mod), func)(user)

获取当前用户登录之后跳转的URL地址openstack_dashboard.views.get_user_home():

[python]  view plain  copy
  1. # 获取当前用户登录之后跳转的URL地址  
  2. def get_user_home(user):  
  3.     dashboard = None  
  4.     if user.is_superuser:# 判断user是否是超级用户  
  5.         # adminy用户不是超级用户没有执行语句块  
  6.         try:  
  7.             dashboard = horizon.get_dashboard('admin')  
  8.         except base.NotRegistered:  
  9.             pass  
  10.   
  11.     if dashboard is None:  
  12.         # dashboard为None 调用horizon.get_default_dashboard()函数  
  13.         dashboard = horizon.get_default_dashboard()  
  14.     # dashboard: <Dashboard: project>  
  15.     return dashboard.get_absolute_url()  

判断当前登录user是否是superuser,例如:admin不是superuser用户,当前因此执行是:dashboard = horizon.get_default_dashboard(),返回“~horizon.Dashboard”默认实例 ,

地址:horizon.base.Size:def get_default_dashboard(self):

[python]  view plain  copy
  1. def get_default_dashboard(self):  
  2.     """ 
  3.        返回“~horizon.Dashboard”默认实例 
  4.    如果"default_dashboard"在参数"HORIZON_CONFIG"指定了,将返回指定值。 
  5.     如果没有指定将调用"~horizon.get_dashboards"函数返回"default_dashboard"值 
  6.     """  
  7.     # 判断在HORIZON_CONFIG中是否指定了default_dashboard的值  
  8.     if self.default_dashboard:  
  9.         # self:<Site: horizon>  
  10.         # self.default_dashboard:'project'  
  11.         # 调用self._registered()函数传入self.default_dashboard值  
  12.         return self._registered(self.default_dashboard)  
  13.     elif len(self._registry):  
  14.         return self.get_dashboards()[0]  
  15.     else:  
  16.         raise NotRegistered("No dashboard modules have been registered.")  

HORIZON_CONFIG中是否指定了default_dashboard的值:’default_dashboard': ‘project',调用self._registered()函数传入self.default_dashboard值‘project',从已注册的模块中查找project模块并返回该实例对象,

地址:horizon.base.Registry:def _registered(self, cls):

[python]  view plain  copy
  1. def _registered(selfcls):  
  2.     # self:<Site: horizon>  
  3.     # cls:'project'  
  4.     # inspect.isclass:检查对象cls是否是一个类,issubclass:判断cls类是_registerable_class子类或子孙类  
  5.     # self._registerable_class:<class 'horizon.base.Dashboard'> -> class:Site  _registerable_class = Dashboard  
  6.   
  7.     if inspect.isclass(clsand issubclass(clsself._registerable_class):  
  8.         # 因为cls:'project'是一个字符串条件不成立  
  9.         # 例如 cls: <class 'openstack_dashboard.dashboards.settings.dashboard.Settings'> 进入代码块  
  10.         found = self._registry.get(clsNone)  
  11.         if found:  
  12.             return found  
  13.     else:  
  14.         # 循环判断cls是否等于self._registry.values()注册表,如果相等返回这个模块,这个返回<Dashboard: project>  
  15.         # cls="project" -> self._registry.values():[<Dashboard: identity>, <Dashboard: settings>, <Dashboard: project>, <Dashboard: admin>]  
  16.         for registered in self._registry.values():  
  17.             if registered.slug == cls:  
  18.                 return registered  
  19.     class_name = self._registerable_class.__name__  
  20.     if hasattr(self"_registered_with"):  
  21.         parent = self._registered_with._registerable_class.__name__  
  22.         raise NotRegistered('%(type)s with slug "%(slug)s" is not '  
  23.                             'registered with %(parent)s "%(name)s".'  
  24.                             % {"type": class_name,  
  25.                                "slug"cls,  
  26.                                "parent": parent,  
  27.                                "name"self.slug})  
  28.     else:  
  29.         slug = getattr(cls"slug"cls)  
  30.         raise NotRegistered('%(type)s with slug "%(slug)s" is not '  
  31.                             'registered.' % {"type": class_name,  
  32.                                              "slug": slug})  

返回 return <Dashboard:project>,openstack_dashboard.views.get_user_home()中得到返回的<Dashboard:project>对象,接着调用该对象的get_absolute_url()返回该模块的访问地址。

地址:horizon.base.Dashboard:def get_absolute_url(self)

[python]  view plain  copy
  1. def get_absolute_url(self):  
  2.     """返回dashboard的默认URL 
  3.    默认URL定义为:name="index" 
  4.     在URLcon:class:`~horizon.Panel`指定的:attr:`~horizon.Dashboard.default_panel`. 
  5.     """  
  6.     try:  
  7.   
  8.         """ 
  9.            获取Dashboard:Default Panel Url 
  10.            self:<Dashboard: project> 
  11.            self.default_panel:'overview' 
  12.         """  
  13.         return self._registered(self.default_panel).get_absolute_url()  
  14.     except Exception:  
  15.        LOG.exception("Error reversing absolute URL for %s." % self)  
  16.         raise  

获取该Dashboard 默认Panel的URL地址,循环之前返回Dasboard过程,通过horizon.base.Registry:def _registered(self, cls)—> horizon.base.Size:def get_default_dashboard(self)返回注册表中默认的Panel对象,接着调用horizon.base.Panel:defget_absolute_url(self) 返回Panel访问的URL地址:例如:’/project/‘。函数通过调用关系层层返回’/project/‘值,最终返回到openstack_dashboard.views.py:splash(),重定向到/project/地址,显示Project模块内容,Project模块内容加载过程后续博文说明。

原文:http://blog.csdn.net/u011521019/article/details/44738657

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值