Skip to content

Commit

Permalink
[#8] Add tests for documentation management commands
Browse files Browse the repository at this point in the history
  • Loading branch information
pi-sigma committed May 10, 2024
1 parent 17e5a8f commit 5285bbf
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 45 deletions.
21 changes: 11 additions & 10 deletions django_setup_configuration/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.db.models.fields.json import JSONField
from django.db.models.fields.related import ForeignKey, OneToOneField

from .constants import BasicFieldDescription
from .constants import basic_field_description


@dataclass(frozen=True, slots=True)
Expand All @@ -16,7 +16,7 @@ class ConfigField:
verbose_name: str
description: str
default_value: str
values: str
field_description: str


@dataclass
Expand Down Expand Up @@ -78,17 +78,18 @@ def get_default_value(field: models.Field) -> str:
return default

@staticmethod
def get_example_values(field: models.Field) -> str:
def get_field_description(field: models.Field) -> str:
# fields with choices
if choices := field.choices:
values = [choice[0] for choice in choices]
return ", ".join(values)
example_values = [choice[0] for choice in choices]
return ", ".join(example_values)

# other fields
field_type = field.get_internal_type()
field_type = type(field)
match field_type:
case item if item in BasicFieldDescription.names:
return getattr(BasicFieldDescription, field_type)
case item if item in basic_field_description.keys():
return basic_field_description.get(item)
# case item if item in BasicFieldDescription.names:
# return getattr(BasicFieldDescription, field_type)
case _:
return "No information available"

Expand Down Expand Up @@ -146,7 +147,7 @@ def create_config_fields(
verbose_name=model_field.verbose_name,
description=model_field.help_text,
default_value=self.get_default_value(model_field),
values=self.get_example_values(model_field),
field_description=self.get_field_description(model_field),
)

if config_field.name in self.required_fields:
Expand Down
37 changes: 17 additions & 20 deletions django_setup_configuration/constants.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
from django.contrib import postgres
from django.db import models


class BasicFieldDescription(models.TextChoices):
"""
Description of the values for basic Django model fields
"""

ArrayField = "string, comma-delimited ('foo,bar,baz')"
BooleanField = "True, False"
CharField = "string"
FileField = (
basic_field_description = {
postgres.fields.ArrayField: "string, comma-delimited ('foo,bar,baz')",
models.BooleanField: "True, False",
models.CharField: "string",
models.FileField: (
"string represeting the (absolute) path to a file, "
"including file extension: {example}".format(
example="/absolute/path/to/file.xml"
)
)
ImageField = (
),
models.ImageField: (
"string represeting the (absolute) path to an image file, "
"including file extension: {example}".format(
example="/absolute/path/to/image.png"
)
)
IntegerField = "string representing an integer"
JSONField = "Mapping: {example}".format(example="{'some_key': 'Some value'}")
PositiveIntegerField = "string representing a positive integer"
TextField = "text (string)"
URLField = "string (URL)"
UUIDField = "UUID string {example}".format(
),
models.IntegerField: "string representing an integer",
models.JSONField: "Mapping: {example}".format(example="{'some_key': 'Some value'}"),
models.PositiveIntegerField: "string representing a positive integer",
models.TextField: "text (string)",
models.URLField: "string (URL)",
models.UUIDField: "UUID string {example}".format(
example="(e.g. f6b45142-0c60-4ec7-b43d-28ceacdc0b34)"
)
),
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ def check_doc(self, config_option: str) -> None:
if rendered_content != file_content:
raise DocumentationCheckFailed(
"Class {config} has changes which are not reflected in the "
"documentation ({source_path}). "
"documentation ({source_path})."
"Did you forget to run generate_config_docs?\n".format(
config=self.get_config(config_option, class_name_only=True),
source_path=f"{SOURCE_DIR}/{config_option}.rst",
)
)

def handle(self, *args, **kwargs) -> None:
supported_options = self.registry.field_names
supported_options = self.registry.config_model_keys

for option in supported_options:
self.check_doc(option)
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ def get_config(
config_instance = config_model()
return config_instance

def get_detailed_info(self, config: ConfigSettingsModel) -> list[list[str]]:
def get_detailed_info(self, config=ConfigSettingsModel) -> list[list[str]]:
ret = []
for field in config.config_fields.all:
part = []
part.append(f"{'Variable':<20}{config.get_setting_name(field)}")
part.append(f"{'Setting':<20}{field.verbose_name}")
part.append(f"{'Description':<20}{field.description or 'No description'}")
part.append(f"{'Possible values':<20}{field.values}")
part.append(f"{'Possible values':<20}{field.field_description}")
part.append(f"{'Default value':<20}{field.default_value}")
ret.append(part)
return ret
Expand Down Expand Up @@ -91,18 +91,21 @@ def write_doc(self, config_option: str) -> None:
with open(output_path, "w+") as output:
output.write(rendered)

return rendered

def handle(self, *args, **kwargs) -> None:
config_option = kwargs["config_option"]

supported_options = self.registry.field_names
supported_options = self.registry.config_model_keys

if config_option and config_option not in supported_options:
raise ConfigurationException(
f"Unsupported config option ({config_option})\n"
f"Supported: {', '.join(supported_options)}"
)
elif config_option:
self.write_doc(config_option)
rendered = self.write_doc(config_option)
else:
for option in supported_options:
self.write_doc(option)
rendered = self.write_doc(option)
return rendered
13 changes: 7 additions & 6 deletions django_setup_configuration/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,18 @@ def register_config_models(self) -> None:
try:
model = import_string(mapping["model"])
except ImportError as exc:
exc.add_note(
"\nHint: check your settings for django-setup-configuration"
)
raise
raise ImproperlyConfigured(
"\n\nThe class testapp.models.bogus was not found\n"
"Check the DJANGO_SETUP_CONFIG_REGISTER setting for "
"django-setup-configuration"
) from exc
else:
setattr(self, file_name, model)

@property
def fields(self) -> tuple[ConfigSettingsModel, ...]:
def config_models(self) -> tuple[ConfigSettingsModel, ...]:
return tuple(getattr(self, key) for key in vars(self).keys())

@property
def field_names(self) -> tuple[str, ...]:
def config_model_keys(self) -> tuple[str, ...]:
return tuple(key for key in vars(self).keys())
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Documentation = "http://django-setup-configuration.readthedocs.io/en/latest/"

[project.optional-dependencies]
tests = [
"psycopg2",
"pytest",
"pytest-django",
"pytest-mock",
Expand Down Expand Up @@ -75,6 +76,10 @@ sections=["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER
testpaths = ["tests"]
DJANGO_SETTINGS_MODULE = "testapp.settings"

[tool.pytest]
testpaths = ["tests"]
DJANGO_SETTINGS_MODULE = "testapp.settings"

[tool.bumpversion]
current_version = "0.1.0"
files = [
Expand Down
13 changes: 13 additions & 0 deletions testapp/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@
from django.contrib.auth import authenticate
from django.contrib.auth.models import User

from django_setup_configuration.base import ConfigSettingsModel
from django_setup_configuration.configuration import BaseConfigurationStep
from django_setup_configuration.exceptions import SelfTestFailed

from .models import ProductConfig


class ProductConfigurationSettings(ConfigSettingsModel):
model = ProductConfig
display_name = "Product Configuration"
namespace = "PRODUCT"
required_fields = ("name", "service_url")
all_fields = required_fields + (
"tags",
)


class UserConfigurationStep(BaseConfigurationStep):
"""
Expand Down
55 changes: 55 additions & 0 deletions testapp/docs/configuration/product.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.. _product:

=====================
Product Configuration
=====================

Settings Overview
=================

Enable/Disable configuration:
"""""""""""""""""""""""""""""

::

PRODUCT_CONFIG_ENABLE

Required:
"""""""""

::

PRODUCT_NAME
PRODUCT_SERVICE_URL

All settings:
"""""""""""""

::

PRODUCT_NAME
PRODUCT_SERVICE_URL
PRODUCT_TAGS

Detailed Information
====================

::

Variable PRODUCT_NAME
Setting Name
Description The name of the product
Possible values string
Default value No default
Variable PRODUCT_SERVICE_URL
Setting Service url
Description The url of the service
Possible values string (URL)
Default value No default
Variable PRODUCT_TAGS
Setting tags
Description Tags for the product
Possible values string, comma-delimited ('foo,bar,baz')
Default value example_tag
34 changes: 34 additions & 0 deletions testapp/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models


class Service(models.Model):
url = models.URLField(
verbose_name="Service url",
help_text="The url of the service",
)
bogus = models.CharField(
verbose_name="Bogus service field", help_text="Should not be included in docs"
)


class ProductConfig(models.Model):
name = models.CharField(
verbose_name="Name",
help_text="The name of the product",
)
service = models.OneToOneField(
to=Service,
verbose_name="Service",
default=None,
on_delete=models.SET_NULL,
help_text="API service of the product",
)
tags = ArrayField(
base_field=models.CharField("Product tag"),
default=["example_tag"],
help_text="Tags for the product",
)
bogus = models.CharField(
help_text="Should be excluded",
)
15 changes: 13 additions & 2 deletions testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "django_setup_configuration.db",
"ENGINE": "django.db.backends.postgresql",
"NAME": "postgres",
"USER": "postgres",
"PASSWORD": "postgres",
}
}

Expand Down Expand Up @@ -64,3 +66,12 @@
USER_CONFIGURATION_ENABLED = os.getenv("USER_CONFIGURATION_ENABLED", True)
USER_CONFIGURATION_USERNAME = os.getenv("USER_CONFIGURATION_USERNAME", "demo")
USER_CONFIGURATION_PASSWORD = os.getenv("USER_CONFIGURATION_PASSWORD", "secret")

DJANGO_SETUP_CONFIG_REGISTER = [
{
"model": "testapp.configuration.ProductConfigurationSettings",
"file_name": "product",
}
]
DJANGO_SETUP_CONFIG_TEMPLATE_NAME = "testapp/config_doc.rst"
DJANGO_SETUP_CONFIG_DOC_DIR = "testapp/docs/configuration"
46 changes: 46 additions & 0 deletions testapp/templates/testapp/config_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{% block link %}{{ link }}{% endblock %}

{% block title %}{{ title }}{% endblock %}

Settings Overview
=================

Enable/Disable configuration:
"""""""""""""""""""""""""""""

::

{% spaceless %}
{{ enable_settings }}
{% endspaceless %}

Required:
"""""""""

::

{% spaceless %}
{% for setting in required_settings %}{{ setting }}
{% endfor %}
{% endspaceless %}

All settings:
"""""""""""""

::

{% spaceless %}
{% for setting in all_settings %}{{ setting }}
{% endfor %}
{% endspaceless %}

Detailed Information
====================

::

{% spaceless %}
{% for detail in detailed_info %}
{% for part in detail %}{{ part|safe }}
{% endfor %}{% endfor %}
{% endspaceless %}
3 changes: 3 additions & 0 deletions tests/mocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mock_product_doc = (
'.. _product:\n\n=====================\nProduct Configuration\n=====================\n\nSettings Overview\n=================\n\nEnable/Disable configuration:\n"""""""""""""""""""""""""""""\n\n::\n\n PRODUCT_CONFIG_ENABLE\n\nRequired:\n"""""""""\n\n::\n\n PRODUCT_NAME\n PRODUCT_SERVICE_URL\n\nAll settings:\n"""""""""""""\n\n::\n\n PRODUCT_NAME\n PRODUCT_SERVICE_URL\n PRODUCT_TAGS\n\nDetailed Information\n====================\n\n::\n\n Variable PRODUCT_NAME\n Setting Name\n Description The name of the product\n Possible values string\n Default value No default\n \n Variable PRODUCT_SERVICE_URL\n Setting Service url\n Description The url of the service\n Possible values string (URL)\n Default value No default\n \n Variable PRODUCT_TAGS\n Setting tags\n Description Tags for the product\n Possible values string, comma-delimited (\'foo,bar,baz\')\n Default value example_tag\n'
)
Loading

0 comments on commit 5285bbf

Please sign in to comment.