mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed #5405 -- Added admindocs support for reStructured text in model docstrings
Thanks elvard and gkmngrgn for work on the patch and Markus H. for review.
This commit is contained in:
		
							
								
								
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -701,6 +701,7 @@ answer newbie questions, and generally made Django that much better: | |||||||
|     Zach Thompson <zthompson47@gmail.com> |     Zach Thompson <zthompson47@gmail.com> | ||||||
|     Zain Memon |     Zain Memon | ||||||
|     Zak Johnson <zakj@nox.cx> |     Zak Johnson <zakj@nox.cx> | ||||||
|  |     Žan Anderle <zan.anderle@gmail.com> | ||||||
|     Zbigniew Siciarz <zbigniew@siciarz.net> |     Zbigniew Siciarz <zbigniew@siciarz.net> | ||||||
|     zegor |     zegor | ||||||
|     Zlatko Mašek <zlatko.masek@gmail.com> |     Zlatko Mašek <zlatko.masek@gmail.com> | ||||||
|   | |||||||
| @@ -22,11 +22,10 @@ | |||||||
|  |  | ||||||
| {% block content %} | {% block content %} | ||||||
| <div id="content-main"> | <div id="content-main"> | ||||||
| <h1>{{ summary }}</h1> | <h1>{{ name }}</h1> | ||||||
|  | <h2 class="subhead">{{ summary }}</h2> | ||||||
|  |  | ||||||
| {% if description %} | {{ description }} | ||||||
|   <p>{% filter linebreaksbr %}{% trans description %}{% endfilter %}</p> |  | ||||||
| {% endif %} |  | ||||||
|  |  | ||||||
| <div class="module"> | <div class="module"> | ||||||
| <table class="model"> | <table class="model"> | ||||||
|   | |||||||
| @@ -178,18 +178,25 @@ class ModelDetailView(BaseAdminDocsView): | |||||||
|     template_name = 'admin_doc/model_detail.html' |     template_name = 'admin_doc/model_detail.html' | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs): |     def get_context_data(self, **kwargs): | ||||||
|  |         model_name = self.kwargs['model_name'] | ||||||
|         # Get the model class. |         # Get the model class. | ||||||
|         try: |         try: | ||||||
|             app_config = apps.get_app_config(self.kwargs['app_label']) |             app_config = apps.get_app_config(self.kwargs['app_label']) | ||||||
|         except LookupError: |         except LookupError: | ||||||
|             raise Http404(_("App %(app_label)r not found") % self.kwargs) |             raise Http404(_("App %(app_label)r not found") % self.kwargs) | ||||||
|         try: |         try: | ||||||
|             model = app_config.get_model(self.kwargs['model_name']) |             model = app_config.get_model(model_name) | ||||||
|         except LookupError: |         except LookupError: | ||||||
|             raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % self.kwargs) |             raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % self.kwargs) | ||||||
|  |  | ||||||
|         opts = model._meta |         opts = model._meta | ||||||
|  |  | ||||||
|  |         title, body, metadata = utils.parse_docstring(model.__doc__) | ||||||
|  |         if title: | ||||||
|  |             title = utils.parse_rst(title, 'model', _('model:') + model_name) | ||||||
|  |         if body: | ||||||
|  |             body = utils.parse_rst(body, 'model', _('model:') + model_name) | ||||||
|  |  | ||||||
|         # Gather fields/field descriptions. |         # Gather fields/field descriptions. | ||||||
|         fields = [] |         fields = [] | ||||||
|         for field in opts.fields: |         for field in opts.fields: | ||||||
| @@ -271,9 +278,8 @@ class ModelDetailView(BaseAdminDocsView): | |||||||
|             }) |             }) | ||||||
|         kwargs.update({ |         kwargs.update({ | ||||||
|             'name': '%s.%s' % (opts.app_label, opts.object_name), |             'name': '%s.%s' % (opts.app_label, opts.object_name), | ||||||
|             # Translators: %s is an object type name |             'summary': title, | ||||||
|             'summary': _("Attributes on %s objects") % opts.object_name, |             'description': body, | ||||||
|             'description': model.__doc__, |  | ||||||
|             'fields': fields, |             'fields': fields, | ||||||
|         }) |         }) | ||||||
|         return super(ModelDetailView, self).get_context_data(**kwargs) |         return super(ModelDetailView, self).get_context_data(**kwargs) | ||||||
|   | |||||||
| @@ -76,6 +76,11 @@ Minor features | |||||||
|   <django.contrib.admin.ModelAdmin.show_full_result_count>` to control whether |   <django.contrib.admin.ModelAdmin.show_full_result_count>` to control whether | ||||||
|   or not the full count of objects should be displayed on a filtered admin page. |   or not the full count of objects should be displayed on a filtered admin page. | ||||||
|  |  | ||||||
|  | :mod:`django.contrib.admindocs` | ||||||
|  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||||
|  |  | ||||||
|  | * reStructuredText is now parsed in model docstrings. | ||||||
|  |  | ||||||
| :mod:`django.contrib.auth` | :mod:`django.contrib.auth` | ||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,6 +18,18 @@ class Family(models.Model): | |||||||
|  |  | ||||||
|  |  | ||||||
| class Person(models.Model): | class Person(models.Model): | ||||||
|  |     """ | ||||||
|  |     Stores information about a person, related to :model:`myapp.Company`. | ||||||
|  |  | ||||||
|  |     **Notes** | ||||||
|  |  | ||||||
|  |     Use ``save_changes()`` when saving this object. | ||||||
|  |  | ||||||
|  |     ``company`` | ||||||
|  |         Field storing :model:`myapp.Company` where the person works. | ||||||
|  |  | ||||||
|  |     (DESCRIPTION) | ||||||
|  |     """ | ||||||
|     first_name = models.CharField(max_length=200, help_text="The person's first name") |     first_name = models.CharField(max_length=200, help_text="The person's first name") | ||||||
|     last_name = models.CharField(max_length=200, help_text="The person's last name") |     last_name = models.CharField(max_length=200, help_text="The person's last name") | ||||||
|     company = models.ForeignKey(Company, help_text="place of work") |     company = models.ForeignKey(Company, help_text="place of work") | ||||||
|   | |||||||
| @@ -291,3 +291,110 @@ class TestModelDetailView(AdminDocsTestCase): | |||||||
|  |  | ||||||
|         fields = response.context_data.get('fields') |         fields = response.context_data.get('fields') | ||||||
|         self.assertEqual(len(fields), 2) |         self.assertEqual(len(fields), 2) | ||||||
|  |  | ||||||
|  |     def test_model_docstring_renders_correctly(self): | ||||||
|  |         summary = ( | ||||||
|  |             '<h2 class="subhead"><p>Stores information about a person, related to <a class="reference external" ' | ||||||
|  |             'href="/admindocs/models/myapp.company/">myapp.Company</a>.</p></h2>' | ||||||
|  |         ) | ||||||
|  |         subheading = '<p><strong>Notes</strong></p>' | ||||||
|  |         body = '<p>Use <tt class="docutils literal">save_changes()</tt> when saving this object.</p>' | ||||||
|  |         model_body = ( | ||||||
|  |             '<dl class="docutils"><dt><tt class="' | ||||||
|  |             'docutils literal">company</tt></dt><dd>Field storing <a class="' | ||||||
|  |             'reference external" href="/admindocs/models/myapp.company/">' | ||||||
|  |             'myapp.Company</a> where the person works.</dd></dl>' | ||||||
|  |         ) | ||||||
|  |         self.assertContains(self.response, 'DESCRIPTION') | ||||||
|  |         self.assertContains(self.response, summary, html=True) | ||||||
|  |         self.assertContains(self.response, subheading, html=True) | ||||||
|  |         self.assertContains(self.response, body, html=True) | ||||||
|  |         self.assertContains(self.response, model_body, html=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @unittest.skipUnless(utils.docutils_is_available, "no docutils installed.") | ||||||
|  | class TestUtils(AdminDocsTestCase): | ||||||
|  |     """ | ||||||
|  |     This __doc__ output is required for testing. I copied this example from | ||||||
|  |     `admindocs` documentation. (TITLE) | ||||||
|  |  | ||||||
|  |     Display an individual :model:`myapp.MyModel`. | ||||||
|  |  | ||||||
|  |     **Context** | ||||||
|  |  | ||||||
|  |     ``RequestContext`` | ||||||
|  |  | ||||||
|  |     ``mymodel`` | ||||||
|  |         An instance of :model:`myapp.MyModel`. | ||||||
|  |  | ||||||
|  |     **Template:** | ||||||
|  |  | ||||||
|  |     :template:`myapp/my_template.html` (DESCRIPTION) | ||||||
|  |  | ||||||
|  |     some_metadata: some data | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.docstring = self.__doc__ | ||||||
|  |  | ||||||
|  |     def test_trim_docstring(self): | ||||||
|  |         trim_docstring_output = utils.trim_docstring(self.docstring) | ||||||
|  |         trimmed_docstring = ( | ||||||
|  |             'This __doc__ output is required for testing. I copied this ' | ||||||
|  |             'example from\n`admindocs` documentation. (TITLE)\n\n' | ||||||
|  |             'Display an individual :model:`myapp.MyModel`.\n\n' | ||||||
|  |             '**Context**\n\n``RequestContext``\n\n``mymodel``\n' | ||||||
|  |             '    An instance of :model:`myapp.MyModel`.\n\n' | ||||||
|  |             '**Template:**\n\n:template:`myapp/my_template.html` ' | ||||||
|  |             '(DESCRIPTION)\n\nsome_metadata: some data' | ||||||
|  |         ) | ||||||
|  |         self.assertEqual(trim_docstring_output, trimmed_docstring) | ||||||
|  |  | ||||||
|  |     def test_parse_docstring(self): | ||||||
|  |         title, description, metadata = utils.parse_docstring(self.docstring) | ||||||
|  |         docstring_title = ( | ||||||
|  |             'This __doc__ output is required for testing. I copied this example from\n' | ||||||
|  |             '`admindocs` documentation. (TITLE)' | ||||||
|  |         ) | ||||||
|  |         docstring_description = ( | ||||||
|  |             'Display an individual :model:`myapp.MyModel`.\n\n' | ||||||
|  |             '**Context**\n\n``RequestContext``\n\n``mymodel``\n' | ||||||
|  |             '    An instance of :model:`myapp.MyModel`.\n\n' | ||||||
|  |             '**Template:**\n\n:template:`myapp/my_template.html` ' | ||||||
|  |             '(DESCRIPTION)' | ||||||
|  |         ) | ||||||
|  |         self.assertEqual(title, docstring_title) | ||||||
|  |         self.assertEqual(description, docstring_description) | ||||||
|  |         self.assertEqual(metadata, {'some_metadata': 'some data'}) | ||||||
|  |  | ||||||
|  |     def test_title_output(self): | ||||||
|  |         title, description, metadata = utils.parse_docstring(self.docstring) | ||||||
|  |         title_output = utils.parse_rst(title, 'model', 'model:admindocs') | ||||||
|  |         self.assertIn('TITLE', title_output) | ||||||
|  |  | ||||||
|  |         title_rendered = ( | ||||||
|  |             '<p>This __doc__ output is required for testing. I copied this ' | ||||||
|  |             'example from\n<a class="reference external" ' | ||||||
|  |             'href="/admindocs/models/admindocs/">admindocs</a> documentation. ' | ||||||
|  |             '(TITLE)</p>\n' | ||||||
|  |         ) | ||||||
|  |         self.assertHTMLEqual(title_output, title_rendered) | ||||||
|  |  | ||||||
|  |     def test_description_output(self): | ||||||
|  |         title, description, metadata = utils.parse_docstring(self.docstring) | ||||||
|  |         description_output = utils.parse_rst(description, 'model', 'model:admindocs') | ||||||
|  |  | ||||||
|  |         description_rendered = ( | ||||||
|  |             '<p>Display an individual <a class="reference external" ' | ||||||
|  |             'href="/admindocs/models/myapp.mymodel/">myapp.MyModel</a>.</p>\n' | ||||||
|  |             '<p><strong>Context</strong></p>\n<p><tt class="docutils literal">' | ||||||
|  |             'RequestContext</tt></p>\n<dl class="docutils">\n<dt><tt class="' | ||||||
|  |             'docutils literal">mymodel</tt></dt>\n<dd>An instance of <a class="' | ||||||
|  |             'reference external" href="/admindocs/models/myapp.mymodel/">' | ||||||
|  |             'myapp.MyModel</a>.</dd>\n</dl>\n<p><strong>Template:</strong></p>' | ||||||
|  |             '\n<p><a class="reference external" href="/admindocs/templates/' | ||||||
|  |             'myapp/my_template.html/">myapp/my_template.html</a> (DESCRIPTION)' | ||||||
|  |             '</p>\n' | ||||||
|  |         ) | ||||||
|  |         self.assertHTMLEqual(description_output, description_rendered) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user