Skip to content

Instantly share code, notes, and snippets.

@rgutierrez-cotech
Created January 27, 2021 18:57
Show Gist options
  • Save rgutierrez-cotech/cf09f015dadd214d6c82b772b7d87be2 to your computer and use it in GitHub Desktop.
Save rgutierrez-cotech/cf09f015dadd214d6c82b772b7d87be2 to your computer and use it in GitHub Desktop.
Context model full version
class Context(models.Model):
"""
A through model for connecting two models together. Only
one field should have a value; the rest should remain null.
The models linked in the one-to-one fields here will be the
parent, or context, of a single other model instance, which
will use a ForeignKey to Context. Any time we add a model
that can be a context to another model, we need to add a
new field to the model here.
This model is used instead of GenericForeignKey; it allows
us to specify a `context` ForeignKey on any model, and then
define inside Context here which model types can be "contexts".
"""
activity = models.OneToOneField(
'improvement_library.Activity',
null=True, blank=True,
on_delete=models.CASCADE)
post = models.OneToOneField(
'improvement_library.Post',
null=True, blank=True,
on_delete=models.CASCADE)
announcement = models.OneToOneField(
'improvement_library.Announcement',
null=True, blank=True,
on_delete=models.CASCADE)
group = models.OneToOneField(
'improvement_library.Group',
null=True, blank=True,
on_delete=models.CASCADE)
chcomment = models.OneToOneField(
'improvement_library.CHComment',
null=True, blank=True,
on_delete=models.CASCADE)
def clean_fields(self, exclude=None):
"""
Ensure only one field has a value at any time
"""
# there's probably a better way to do this
def _sum_field_has_val(prev, cur):
if cur:
return prev + 1
else:
return prev
field_values = [0] + [ getattr(self, field.name, None) \
for field in self._meta.get_fields() ]
if reduce(_sum_field_has_val, field_values) > 1:
raise ValidationError('Only one field can have a value at a time!')
super().clean_fields(exclude=exclude)
@classmethod
def get_or_create(cls, context_type, context):
if isinstance(context, (int, str)):
context_args = {}
context_args['{}_id'.format(context_type)] = context
else:
context_args = {context_type: context}
try:
c = cls.objects.get(**context_args)
except Context.DoesNotExist:
c = cls(**context_args)
c.save()
return c
def save(self, *args, **kwargs):
"""
Performs some initial checks and verifications before
saving the instance.
Clean manually since we are not using a ModelForm to
instantiate
"""
self.full_clean()
return super().save(*args, **kwargs)
def get_context_field_name(obj):
"""
A helper function used throughout the "context" interface
Get the "fake" context field from an object, either a db
model or a dict-like object.
"""
if isinstance(obj, ContextualModel):
dict_like_object = obj.__dict__
else:
dict_like_object = obj
for field_name, field_val in dict_like_object.items():
if field_name.startswith('context_'):
return field_name
raise AttributeError("No initial context property found on the class instance or dictionary object. "
"Please set a 'context_<obj_name>' property/key on the class/dictionary, where <obj_name> "
"is the lowercase name of a model; give it an instance of the model or id of a model "
"as a value.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment