nucleus/base/models.py (81 lines of code) (raw):
import json
from logging import getLogger
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.signals import m2m_changed, post_save
from django.dispatch import receiver
from django.utils.timezone import now
from crum import get_current_user
from django_extensions.db.fields import CreationDateTimeField
from nucleus.base.tasks import tasks
log = getLogger(__name__)
M2M_ACTIONS = ["post_add", "post_remove", "post_clear"]
DEFAULT_BRANCH = settings.GITHUB_OUTPUT_BRANCH
def send_instance_to_github(instance, branch=DEFAULT_BRANCH):
log.debug(f"send_instance_to_github, {instance._meta.label_lower}, {instance.pk}")
if not settings.GITHUB_PUSH_ENABLE:
return
author = get_current_user()
ghl = GithubLog.objects.create(
content_object=instance,
author=author,
branch=branch,
)
tasks.schedule("nucleus:save_to_github", ghl.pk)
@receiver(post_save, weak=False, dispatch_uid="send_to_github_signal")
def send_to_github_signal(sender, instance, **kwargs):
if issubclass(sender, SaveToGithubModel):
log.debug(f"send_to_github_signal, {sender._meta}, {instance.pk}")
instance.to_github()
@receiver(m2m_changed, weak=False, dispatch_uid="send_to_github_m2m")
def send_to_github_m2m(sender, instance, action, reverse, model, pk_set, **kwargs):
log.debug(f"send_to_github_m2m, {model._meta}, {action}, {pk_set}")
if action in M2M_ACTIONS and issubclass(model, SaveToGithubModel):
for obj in model.objects.filter(pk__in=pk_set):
obj.to_github()
class TimeStampedModel(models.Model):
"""
Replacement for django_extensions.db.models.TimeStampedModel
that updates the modified timestamp by default, but allows
that behavior to be overridden by passing a modified=False
parameter to the save method
"""
created = CreationDateTimeField()
modified = models.DateTimeField(editable=False, blank=True, db_index=True)
class Meta:
abstract = True
def save(self, *args, **kwargs):
if kwargs.pop("modified", True):
self.modified = now()
super().save(*args, **kwargs)
class SaveToGithubModel(TimeStampedModel):
"""
Abstract model class that adds support for outputting JSON files.
Class must define:
- a `to_dict()` method
- a `slug` field for unique file naming
- a `git_path` variable for the path within the git repo for the files of the model's type
if not defined it will be the verbose plural name of the model
"""
related_field_to_github = None
class Meta:
abstract = True
@property
def git_path(self):
"""Return a string path within the output git repo for files of this type"""
return str(self._meta.verbose_name_plural)
@property
def json_file_path(self):
return "/".join((self.git_path, f"{self.slug}.json"))
def to_json(self):
"""Return a JSON encoded string of the data from to_dict()"""
return json.dumps(self.to_dict(), indent=2, sort_keys=True)
def to_github(self):
if self.related_field_to_github:
field = getattr(self, self.related_field_to_github)
for obj in field.all():
obj.to_github()
else:
send_instance_to_github(self)
class GithubLog(TimeStampedModel):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
branch = models.CharField(max_length=100, default="master")
ack = models.BooleanField(default=False)
fail_count = models.PositiveSmallIntegerField(default=0)
class Meta:
ordering = ["-created"]
get_latest_by = "created"
def __str__(self):
return f"GithubLog: {self.content_object}, {self.author}, {self.branch}"
def author_name(self):
return self.author.get_full_name() or self.author.username