I'd like to have a Django model with a reserved field, so that no one can set it directly but its value it's generated at saving time. This is useful for example to generate user tokens and I want to prevent developers to directly set a value for the token key. At the same time I would like to be able to treat that field as I do with others, so using __ for fields lookup in queries, or be able to retrieve tokens as:
token = Token.objects.get(key='c331054c00494f6a22f0ebde7a32bf9d4619b988')
So in my mind doing something like:
Token.key = 'my-token-key'
should fail, and even instantiation should fail:
token = Token(key='my-token-key')
So far I came up with this solution, but I'm a bit concerned my changes could break some Django workflow since I'm not sure what my changes will affect:
import binascii
import datetime
import os
from django.contrib.auth import get_user_model
from django.db import models
class Token(models.Model):
"""
An access token that is associated with a user.
"""
id = models.AutoField(primary_key=True)
# By default `get_attname` returns the field `name`,
# but in my case the attribute name is different
_key = models.CharField(max_length=40, unique=True, name='key', db_column='key')
_key.get_attname = lambda: '_key'
name = models.CharField(max_length=255)
user = models.ForeignKey(get_user_model(), related_name='tokens')
created = models.DateTimeField(auto_now_add=True)
last_access_time = models.DateTimeField(null=True, blank=True)
expires = models.DateField(
null=True,
blank=True,
help_text="Leave empty for non-expiring tokens. "
"Once the token has expired you can not extend its validity.",
)
@property
def key(self):
return self._key
@key.setter
def key(self, value):
raise ValueError("Can not set key directly. It is automatically generated when saving the model.")
def save(self, *args, **kwargs):
if not self._key:
self._key = self._generate_key()
super(Token, self).save(*args, **kwargs)
@staticmethod
def _generate_key():
return binascii.hexlify(os.urandom(20)).decode()
@property
def expired(self):
return bool(self.expires and self.expires < datetime.date.today())
def __str__(self):
return '{} - {}'.format(self.user, self.name)
class Meta:
verbose_name = "User Token"
verbose_name_plural = "User Tokens"
unique_together = (('name', 'user'),)
As you can see I tried overriding the get_attname method of the key field (needed because the field name and the property are the same and it would lead to errors loading forms). This seems to work just fine, but I would like to know if this could lead to problems running queries.
Maybe there is a simpler way to do this but I couldn't find anything better.
P.S.: I'm using python2 with Django 1.11
Thanks a lot to everyone!