Skip to content

Commit

Permalink
Improve docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jacklinke committed Oct 28, 2024
1 parent 160b6aa commit d92d84f
Show file tree
Hide file tree
Showing 6 changed files with 502 additions and 37 deletions.
4 changes: 2 additions & 2 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "3.11"
python: "3.12"

sphinx:
configuration: docs/conf.py
Expand Down
48 changes: 27 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,38 @@

**Empowering Your SaaS Tenants with Custom Options and Sane Defaults**

`django-tenant-options` provides a powerful and flexible way for your SaaS application’s tenants to customize the selectable options in user-facing forms. This package allows you to offer a balance between providing default options—either mandatory or optional—and giving your tenants the freedom to define their own custom choices, all within a structured framework that ensures consistency and ease of use.

## So your SaaS tenants want to provide end users with choices in a form...

*How can you implement this?*

- **CharField with TextChoices or IntegerChoices**: Define a fixed set of options in your model. This approach is inflexible and doesn't allow for any customization by tenants. What one tenant needs may not be what another tenant needs.
- **ManyToManyField with a custom model**: Create a custom model to store options and use a ManyToManyField in your form. But what if one tenant wants to add a new option? Or if you would like to provide some default options? Or if not every tenant needs to show all of your defaults?
- **JSON Fields**: Store custom options as JSON in a single field. This can be difficult to query and manage, and doesn't provide a structured way to define defaults. And it has all of the problems of the ManyToManyField approach.
- **`django-tenant-options`**: A structured and flexible solution that allows tenants to define their own sets of values for form input while still allowing you, the developer, to offer global defaults (both mandatory and optional).

## Why Use django-tenant-options?

In a SaaS environment, one size doesn't fit all. Tenants often have unique needs for the choices they offer in user-facing forms, but building an entirely custom solution for each tenant - or requiring each tenant to define their own options from scratch - can be complex and time-consuming. `django-tenant-options` addresses this challenge by offering:

- **Flexibility**: Tenants can tailor the options available in forms to better meet their specific needs.
- **Control**: Provide mandatory defaults to maintain a consistent experience across all tenants, while still allowing for customization.
- **Scalability**: Easily manage multiple tenants with differing requirements without compromising on performance or maintainability.
- **Simplicity**: Avoid the complexity of dynamic models or JSON fields, offering a more structured and maintainable solution.
- **CharField with TextChoices or IntegerChoices**: Define a fixed set of options in your model.
- ❌ One size fits all
- ❌ No customization for tenants
- ❌ Code changes required for new options
- **ManyToManyField with a custom model**: Create a custom model to store options and use a ManyToManyField in your form.
- ❌ No distinction between tenant options
- ❌ Complex to manage defaults
- ❌ Hard to maintain consistency
- **JSON Fields**: Store custom options as JSON in a single field.
- ❌ No schema validation
- ❌ No referential integrity
- **Custom Tables Per Tenant**
- ❌ Schema complexity
- ❌ Migration nightmares
- ❌ Performance issues
- **django-tenant-options**:
- ✅ Structured and flexible
- ✅ Allows tenants to define their own sets of values for form inputs
- ✅ Allows you, the developer, to offer global defaults (both mandatory and optional)

In a SaaS environment, one size doesn't fit all. Tenants often have unique needs for the choices they offer in user-facing forms, but building an entirely custom solution for each tenant - or requiring each tenant to define their own options from scratch - can be complex and time-consuming.

## Key Features

- **Customizable Options**: Allow tenants to define their own sets of values for form input while still offering global defaults.
- **Mandatory and Optional Defaults**: Define which options are mandatory for all tenants and which can be optionally used by tenants in their forms.
- **Seamless Integration**: Designed to work smoothly with your existing Django models, making it easy to integrate into your project.
- **Tenant-Specific Logic**: Built-in support for tenant-specific logic, ensuring that each tenant’s unique needs are met.
- **Seamless Integration**: Works with your existing Django models, making it easy to integrate into your project.
- **Tenant-Specific Logic**: Built-in support for tenant-specific logic, so each tenant’s unique needs can be met.

## Potential Use-Cases

Expand Down Expand Up @@ -100,7 +106,6 @@ We will define a very basic `Tenant` model and a `Task` model to illustrate the
from django.contrib.auth import get_user_model
from django.db import models


User = get_user_model()


Expand Down Expand Up @@ -172,7 +177,6 @@ from django.db import models

from example.models import TaskPriorityOption, TaskStatusOption, User


User = get_user_model()


Expand Down Expand Up @@ -210,7 +214,9 @@ class Task(models.Model):

### Forms

`django-tenant-options` provides a set of form mixins and fields to manage the options and selections for each tenant. You can use these forms in your views to allow tenants to customize their options.
`django-tenant-options` provides a set of form mixins and fields to manage the options and selections for each tenant.

You can use these forms in your views to allow tenants to customize their options.

- `OptionCreateFormMixin` and `OptionUpdateFormMixin` are provided to create and update Options.
- `SelectionForm` is used to manage the Selections associated with a tenant.
Expand Down Expand Up @@ -275,6 +281,6 @@ python manage.py syncoptions

## Conclusion

`django-tenant-options` makes it easy to provide your SaaS application’s tenants with customizable form options, while still maintaining the consistency and control needed to ensure a smooth user experience. Whether you're managing project tasks, HR roles, marketplace filters, or any other customizable value sets, this package offers a robust solution.
`django-tenant-options` makes it easy to provide your SaaS application’s tenants with customizable form options, while maintaining consistency and control.

Explore the [full documentation](https://django-tenant-options.readthedocs.io/en/latest/) for more details and start empowering your tenants today!
144 changes: 137 additions & 7 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,152 @@
"""Sphinx configuration."""
"""Sphinx configuration for django-tenant-options documentation."""

import django
from django.conf import settings
import inspect
import os
import sys
from datetime import datetime

import django
from django.utils.html import strip_tags

settings.configure(DEBUG=True)

# Initialize Django
sys.path.insert(0, os.path.abspath(".."))
os.environ["DJANGO_SETTINGS_MODULE"] = "example_project.settings"
django.setup()


# Project information
project = "django-tenant-options"
author = "Jack Linke"
copyright = "2024, Jack Linke"
copyright = f"{datetime.now().year}, {author}"

# General configuration
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx_click",
"myst_parser",
]
autodoc_typehints = "description"

# Any paths that contain templates here, relative to this directory.
# templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"

# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages.
html_theme = "furo"

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ["_static"]

# -- Extension configuration -------------------------------------------------

# Napoleon settings
napoleon_google_docstring = True
napoleon_numpy_docstring = False
napoleon_include_init_with_doc = False
napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = True
napoleon_use_admonition_for_examples = False
napoleon_use_admonition_for_notes = False
napoleon_use_admonition_for_references = False
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
napoleon_preprocess_types = False
napoleon_type_aliases = None
napoleon_attr_annotations = True

# Autodoc settings
autodoc_typehints = "description"
autodoc_default_options = {
"members": True,
"special-members": "__init__",
"exclude-members": "__weakref__",
}
autodoc_mock_imports = [
"django",
] # Add any modules that might cause import errors during doc building

# Intersphinx settings
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"django": ("https://docs.djangoproject.com/en/stable/", "https://docs.djangoproject.com/en/stable/_objects/"),
}

# MyST Parser settings
myst_enable_extensions = [
"amsmath",
"colon_fence",
"deflist",
"dollarmath",
"html_admonition",
"html_image",
"linkify",
"replacements",
"smartquotes",
"substitution",
"tasklist",
]


def project_django_models(app, what, name, obj, options, lines): # pylint: disable=W0613 disable=R0913
"""Process Django models for autodoc.
From: https://djangosnippets.org/snippets/2533/
"""
from django.db import models # pylint: disable=C0415

# Only look at objects that inherit from Django's base model class
if inspect.isclass(obj) and issubclass(obj, models.Model):
# Grab the field list from the meta class
fields = obj._meta.get_fields() # pylint: disable=W0212

for field in fields:
# If it's a reverse relation, skip it
if isinstance(
field,
(
models.fields.related.ManyToOneRel,
models.fields.related.ManyToManyRel,
models.fields.related.OneToOneRel,
),
):
continue

# Decode and strip any html out of the field's help text
help_text = strip_tags(field.help_text) if hasattr(field, "help_text") else None

# Decode and capitalize the verbose name, for use if there isn't
# any help text
verbose_name = field.verbose_name if hasattr(field, "verbose_name") else ""

if help_text:
# Add the model field to the end of the docstring as a param
# using the help text as the description
lines.append(f":param {field.attname}: {help_text}")
else:
# Add the model field to the end of the docstring as a param
# using the verbose name as the description
lines.append(f":param {field.attname}: {verbose_name}")

# Add the field's type to the docstring
lines.append(f":type {field.attname}: {field.__class__.__name__}")

# Return the extended docstring
return lines


def setup(app):
"""Register the Django model processor with Sphinx."""
app.connect("autodoc-process-docstring", project_django_models)
6 changes: 6 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,9 @@
(`int`) Provide detailed output of the migration creation process.
```


### `removetriggers`

```{eval-rst}
.. autoclass:: django_tenant_options.management.commands.removetriggers.Command
```
12 changes: 9 additions & 3 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
furo==2024.8.6
sphinx==8.1.3
# Documentation dependencies
Sphinx==8.1.3
sphinx-rtd-theme==2.0.0
sphinx-click==6.0.0
myst_parser==2.0.0
myst-parser==4.0
furo==2024.8.6
linkify-it-py==2.0.3

# Django and related packages (for autodoc)
Django==5.0.8
Loading

0 comments on commit d92d84f

Please sign in to comment.