"""Core way to access configuration"""
from django.apps import apps
from django.conf import settings
from django.db import models
from django.utils.module_loading import import_string
from pghistory import constants
def middleware_methods():
"""
Methods tracked by the pghistory middleware
"""
return getattr(
settings, "PGHISTORY_MIDDLEWARE_METHODS", ("GET", "POST", "PUT", "PATCH", "DELETE")
)
def json_encoder():
"""The JSON encoder when tracking context"""
encoder = getattr(
settings, "PGHISTORY_JSON_ENCODER", "django.core.serializers.json.DjangoJSONEncoder"
)
if isinstance(encoder, str): # pragma: no branch
encoder = import_string(encoder)
return encoder
def base_model():
"""The base model for event models"""
event_model = import_string("pghistory.models.Event")
base_model = getattr(settings, "PGHISTORY_BASE_MODEL", event_model)
if isinstance(base_model, str): # pragma: no cover
base_model = import_string(base_model)
assert issubclass(base_model, event_model)
return base_model
def field():
"""The default configuration for all fields in event models"""
field = getattr(settings, "PGHISTORY_FIELD", Field())
assert isinstance(field, Field)
return field
def related_field():
"""The default configuration for related fields in event models"""
related_field = getattr(settings, "PGHISTORY_RELATED_FIELD", RelatedField())
assert isinstance(related_field, RelatedField)
return related_field
def foreign_key_field():
"""The default configuration for foreign keys in event models"""
foreign_key_field = getattr(settings, "PGHISTORY_FOREIGN_KEY_FIELD", ForeignKey())
assert isinstance(foreign_key_field, ForeignKey)
return foreign_key_field
def context_field():
"""The default field config to use for context in event models"""
# Note: We will be changing the default context field to have on_delete=PROTECT
# in version 3.
context_field = getattr(settings, "PGHISTORY_CONTEXT_FIELD", ContextForeignKey())
assert isinstance(context_field, (ContextForeignKey, ContextJSONField))
return context_field
def context_id_field():
"""
The default field config to use for context ID in event models when context is denormalized
"""
context_id_field = getattr(settings, "PGHISTORY_CONTEXT_ID_FIELD", ContextUUIDField())
assert isinstance(context_id_field, ContextUUIDField)
return context_id_field
def obj_field():
"""The default field config to use for object references in event models"""
obj_field = getattr(settings, "PGHISTORY_OBJ_FIELD", ObjForeignKey())
assert isinstance(obj_field, ObjForeignKey)
return obj_field
def exclude_field_kwargs():
"""
Provide a mapping of field classes to a list of keyword args to ignore
when instantiating the field on the event model.
For example, a field may not allow ``unique`` as a keyword argument.
If so, set ``settings.PGHISTORY_EXCLUDE_FIELD_KWARGS = {"field.FieldClass": ["unique"]}``
"""
exclude_field_kwargs = getattr(settings, "PGHISTORY_EXCLUDE_FIELD_KWARGS", {})
assert isinstance(exclude_field_kwargs, dict)
for val in exclude_field_kwargs.values():
assert isinstance(val, (list, tuple))
# Import strings
exclude_field_kwargs = {
import_string(key) if isinstance(key, str) else key: value
for key, value in exclude_field_kwargs.items()
}
return exclude_field_kwargs
def admin_ordering():
"""The default ordering for the events admin"""
ordering = getattr(settings, "PGHISTORY_ADMIN_ORDERING", "-pgh_created_at") or []
if not isinstance(ordering, (list, tuple)):
ordering = [ordering]
return ordering
def admin_model():
"""The default list display for the events admin"""
return apps.get_model(getattr(settings, "PGHISTORY_ADMIN_MODEL", "pghistory.Events"))
def admin_queryset():
"""The default queryset for the events admin"""
return getattr(
settings, "PGHISTORY_ADMIN_QUERYSET", admin_model().objects.order_by(*admin_ordering())
)
def admin_class():
"""The admin class to use for the events admin.
Must be a subclass of pghistory.admin.EventsAdmin"""
events_admin = import_string("pghistory.admin.EventsAdmin")
admin_class = getattr(settings, "PGHISTORY_ADMIN_CLASS", events_admin)
if isinstance(admin_class, str): # pragma: no cover
admin_class = import_string(admin_class)
assert issubclass(admin_class, events_admin)
return admin_class
def admin_all_events():
"""True if all events should be shown in the admin when there are no filters"""
return getattr(settings, "PGHISTORY_ADMIN_ALL_EVENTS", True)
def admin_list_display():
"""The default list display for the events admin"""
defaults = ["pgh_created_at", "pgh_obj_model", "pgh_obj_id", "pgh_diff"]
if admin_queryset().model._meta.label == "pghistory.MiddlewareEvents":
defaults.extend(["user", "url"])
return getattr(settings, "PGHISTORY_ADMIN_LIST_DISPLAY", defaults)
def _get_kwargs(vals):
return {
key: val
for key, val in vals.items()
if key not in ("self", "kwargs", "__class__") and val is not constants.UNSET
}
[docs]class Field:
"""Configuration for fields.
Provides these default parameters:
* **primary_key** (default=False)
* **unique** (default=False)
* **blank**
* **null**
* **db_index** (default=False)
* **editable**
* **unique_for_date** (default=None)
* **unique_for_month** (default=None)
* **unique_for_year** (default=None)
The default values for the above parameters ensure that
event models don't have unnecessary uniqueness constraints
carried over from the tracked model.
"""
def __init__(
self,
*,
primary_key=constants.UNSET,
unique=constants.UNSET,
blank=constants.UNSET,
null=constants.UNSET,
db_index=constants.UNSET,
editable=constants.UNSET,
unique_for_date=constants.UNSET,
unique_for_month=constants.UNSET,
unique_for_year=constants.UNSET,
):
self._kwargs = _get_kwargs(locals())
self._finalized = False
@property
def kwargs(self):
return {
key: val
for key, val in {**self.get_default_kwargs(), **self._kwargs}.items()
if val is not constants.DEFAULT
}
def get_default_kwargs(self):
return {
**Field(
primary_key=False,
unique=False,
db_index=False,
unique_for_date=None,
unique_for_month=None,
unique_for_year=None,
)._kwargs,
**field()._kwargs,
}
[docs]class ForeignKey(RelatedField):
"""Configuration for foreign keys.
The following parameters can be used:
* **on_delete** (default=models.DO_NOTHING)
* **db_constraint** (default=False)
Arguments for `RelatedField` and `Field` can also be supplied.
Note that db_index is overridden to `True` for all foreign keys
"""
def __init__(self, *, on_delete=constants.UNSET, db_constraint=constants.UNSET, **kwargs):
super().__init__(**kwargs)
self._kwargs.update(_get_kwargs(locals()))
def get_default_kwargs(self):
return {
**super().get_default_kwargs(),
**ForeignKey(on_delete=models.DO_NOTHING, db_index=True, db_constraint=False)._kwargs,
**foreign_key_field()._kwargs,
}
[docs]class ContextForeignKey(ForeignKey):
"""Configuration for the ``pgh_context`` field when a foreign key is used.
Overrides null to ``True``.
"""
def __init__(self, *, null=True, related_query_name=constants.DEFAULT, **kwargs):
# Note: We will be changing the default context field to have on_delete=PROTECT
# in version 3.
super().__init__(null=null, related_query_name=related_query_name, **kwargs)
self._kwargs.update(_get_kwargs(locals()))
[docs]class ContextJSONField(Field):
"""Configuration for the ``pgh_context`` field when denormalized context is used."""
def __init__(self, *, null=True, **kwargs):
super().__init__(null=null, **kwargs)
self._kwargs.update(_get_kwargs(locals()))
[docs]class ContextUUIDField(Field):
"""Configuration for the ``pgh_context_id`` field when denormalized context is used."""
def __init__(self, *, null=True, **kwargs):
super().__init__(null=null, **kwargs)
self._kwargs.update(_get_kwargs(locals()))
[docs]class ObjForeignKey(ForeignKey):
"""Configuration for the ``pgh_obj`` field"""
def __init__(
self, *, related_name=constants.DEFAULT, related_query_name=constants.DEFAULT, **kwargs
):
# Note: We will be changing the default object field to nullable with on_delete=SET_NULL
# in version 3. related_name will also default to "+"
super().__init__(
related_name=related_name, related_query_name=related_query_name, **kwargs
)
self._kwargs.update(_get_kwargs(locals()))