4

Coming from the rails world, I was pleased to find out about mixins. I set up a

Basic mixin

core/mixins.py

from django.db import models
from django.contrib.auth.models import User

class Timestamps(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

And then my Event model in core/my_app/models.py

from core import mixins as core_mixins

class Event(core_mixins.Timestamps):
    # ...

all jolly good, but what if I wanted to extend this a bit and create a more dynamic mixin?

Advanced mixin

core/mixins.py

from django.db import models
from django.contrib.auth.models import User

from django.dispatch import receiver
from django.db.models.signals import pre_save
from cw_core.requestprovider.signals import get_request

class Trackable(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    created_by = models.ForeignKey(User, related_name='created_XXX') # <-- ???
    updated_by = models.ForeignKey(User, related_name='updated_XXX') # <-- ???

    class Meta:
        abstract = True

@receiver(pre_save, sender=Event) # <-- ???
def security_attributes(sender, instance, **kwargs):
    request = get_request()

    instance.updated_by = request.user

    if instance.id is None:
        instance.created_by = request.user

core/my_app/models.py

from core import mixins as core_mixins

class Event(core_mixins.Trackable):
    # ...
  • How would I dynamically set the related_name? I found this question but I have not found the variables I could use in strings, are there any docs?

  • Also how would I dynamically set the sender in the @receiver call?

My attempt would be to set:

@receiver(pre_save, sender=self.__class__)

But I am unsure if this will work? What is the suggested approach?

Community
  • 1
  • 1
davegson
  • 8,205
  • 4
  • 51
  • 71
  • Regariding the `related_name` I found this [neat doc](https://docs.djangoproject.com/en/1.11/ref/models/options/#default-related-name) – davegson May 05 '17 at 09:07

1 Answers1

6

Just use existing functionality https://docs.djangoproject.com/en/1.11/topics/db/models/#abstract-related-name

class Trackable(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    created_by = models.ForeignKey(User, related_name='created_%(class)s')
    updated_by = models.ForeignKey(User, related_name='updated_%(class)s')
    class Meta:
        abstract = True

And for second part there is also solution https://stackoverflow.com/a/17173716/3627387

@receiver(pre_save)
def security_attributes(sender, instance, **kwargs):
    if not issubclass(sender, core_mixins.Trackable):
        return
    request = get_request()

    instance.updated_by = request.user

    if instance.id is None:
        instance.created_by = request.user

Basically you could filter senders inside signal processor.

Also there is django-model-utilshttps://django-model-utils.readthedocs.io/en/latest/models.html#timestampedmodel

Sardorbek Imomaliev
  • 14,861
  • 2
  • 51
  • 63
  • Where can I get "get_request" method from? Is there a stable library to provide it? – foobar Oct 17 '17 at 09:41
  • @foobar It came from OP's code `from cw_core.requestprovider.signals import get_request`, read his question. Looks like it is his internal function – Sardorbek Imomaliev Oct 17 '17 at 10:06
  • @Sardorbek Imomaliev Yes, I figured it came from his code, but was wondering if there is a library that provides this. It seems like a common problem – foobar Oct 17 '17 at 13:01