Reputation: 51
Apologies if this has been answered before but have been searching for hours.
I am trying to do validation on a django rest model with a m2m field. I have a model that has roles assigned to it. I have some logic if the roles overlap with exisiting models and would like to throw a validation error. I have tried a pre_save signal but apparently django does not assign m2m until after save (no id). How do I access the data pre_save? Here is the model:
class ActivityFactory (models.Model):
"""
This provides an interval over which ActivityDemands are auto-generated and ActivityStudyHours (note that activity study hours can be obsolete as all of the same information is already stored here) instead of the traditional method of inputing ActivityDemands per month. The product of Activity and ActivityDemand will still create StudySiteWorkloads.
"""
start_date = models.DateField()
end_date = models.DateField()
hours = models.DecimalField(max_digits=5, decimal_places=2)
notes = models.TextField(blank=True, null=True)
activity = models.ForeignKey(Activity, on_delete=models.CASCADE, related_name='activity_factories')
roles = models.ManyToManyField(Role, blank=True, related_name='activity_factories')
study = models.ForeignKey(Study, on_delete=models.CASCADE, related_name="activity_factories")
trend_curve = models.ForeignKey(TrendCurve, on_delete=models.CASCADE, related_name="activity_factories")
slug = AutoSlugField(populate_from='study', db_index=True, unique=True, null=True)
class Meta:
verbose_name_plural = 'Activity Factories'
def __str__(self):
return "%s: %s : %s" % (self.study.study_name, self.activity.activity_name, self.trend_curve.denominator_name)
Here is the pre_save:
@receiver(pre_save, sender=ActivityFactory)
def validate_factory(sender, instance, **kwargs):
queryset = ActivityFactory.objects.filter(activity=instance.activity, study=instance.study)
roles=instance.roles.objects.all()
for activity_factory in queryset:
if activity_factory.slug!=instance.slug:
if ((activity_factory.start_date <= instance.start_date <= activity_factory.end_date) or (activity_factory.start_date >= instance.end_date >= activity_factory.end_date)) and bool(set(activity_factory.roles.all()) & set(roles)):
raise serializers.ValidationError({'activity':'Date intervals cannot overlap for a given activity in the same study with the same assigned role'}, code=400)
if activity_factory.activity==instance.activity and activity_factory.hours!=instance.hours and bool(set(activity_factory.roles.all()) & set(roles)):
raise serializers.ValidationError({'activity':'The same activity can not have differing hours assigned to the same role within a study. Consider a new activity or keep hours the same'}, code=400)
Upvotes: 3
Views: 1306
Reputation: 51
Think I may have figured it out. you can use the m2m_changed signal as follows:
@receiver(m2m_changed, sender=ActivityFactory.roles.through)
def validate_factory_m2m(sender, instance, **kwargs):
queryset = ActivityFactory.objects.filter(activity=instance.activity, study=instance.study)
roles=instance.roles.all()
for activity_factory in queryset:
if activity_factory.slug!=instance.slug:
if ((activity_factory.start_date <= instance.start_date <= activity_factory.end_date) or (activity_factory.start_date >= instance.end_date >= activity_factory.end_date)) and bool(set(activity_factory.roles.all()) & set(roles)):
instance.delete()
raise serializers.ValidationError({'activity':'Date intervals cannot overlap for a given activity with the same assigned role. Consider removing that role or changing the dates or activity'}, code=400)
if activity_factory.activity==instance.activity and activity_factory.hours!=instance.hours and bool(set(activity_factory.roles.all()) & set(roles)):
raise serializers.ValidationError({'activity':'The same activity can not have differing hours assigned to the same role within a study. Consider a new activity or role, or keep hours the same'}, code=400)
Upvotes: 2