2

I have written a custom save method for a model in order to prevent save of invalid data, but when I want to update the object via admin (just to change some properties), I got an assert error

my model:

class Segment(CoreModel):
    departure = models.ForeignKey(Place, on_delete=models.CASCADE, limit_choices_to=limit_choices_segment,
                                  related_name='departures')
    destination = models.ForeignKey(Place, on_delete=models.CASCADE, limit_choices_to=limit_choices_segment,
                                    related_name='arrivals')
    distance = models.FloatField(help_text='Distance between places in "km"!', null=True, blank=True,
                                 validators=[property_positive_value])
    duration = models.FloatField(help_text='Transfer duration (hours)', null=True, blank=True,
                                 validators=[property_positive_value])
    cost = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,
                               help_text='Price for a transfer! Currency: "UAH"!',
                               validators=[property_positive_value])
    route = models.ForeignKey(Route, on_delete=models.CASCADE, related_name='segments')

    def __str__(self):
        return '{}-{}'.format(self.departure, self.destination)

    def save(self, *args, **kwargs):
        assert self.departure.role not in (Place.DISTRICT, Place.REGION), (
            "Departure couldn't be Region or District")
        assert self.destination.role not in (Place.DISTRICT, Place.REGION), (
            "Destination couldn't be Region orDistrict")
        assert self.destination != self.departure, "Departure couldn't be equal to Destination"
        assert self.route.segment_validate(departure=self.departure, destination=self.destination), (
            'Impossible to add the segment, please check the route!')

        if self.distance is not None:
            assert self.distance > 0, "Distance couldn't be less or equal to '0'!"
        if self.duration is not None:
            assert self.duration > 0, "Duration couldn't be less or equal to '0'!"
        if self.cost is not None:
            assert self.cost > 0, "Cost couldn't be less or equal to '0'!"
        super(Segment, self).save(*args, **kwargs)

validation method:

    def segment_validate(self, departure, destination):
        segments = self.segments.all()
        if segments:
            for segmnet in segments:
                same_departure = segmnet.departure == departure
                same_destination = segmnet.destination == destination
                if ((same_departure and same_destination) or
                        same_departure or same_destination):
                    return False
            if segments.latest('created').destination != departure:
                return False
        return True

the error is here:

assert self.route.segment_validate(departure=self.departure, destination=self.destination), (
            'Impossible to add the segment, please check the route!')

but i didn't change departure and destination Could you help me to avoid this error?

roziuk
  • 61
  • 7
  • Well since you added already that segment, the segment now has a conflict, with itself! – Willem Van Onsem Jun 02 '19 at 09:52
  • @WillemVanOnsem, do you have any idea how to fix it? because i can't skip my validation method that causes the error? – roziuk Jun 02 '19 at 10:00
  • What is `self.segments` doeing here by the way? It looks like this is a validator of a `Route` model? – Willem Van Onsem Jun 02 '19 at 10:03
  • @WillemVanOnsem, yes it validator from a `Route` model, it checks is it possible to add new segment to the route – roziuk Jun 02 '19 at 10:09
  • Your asserts in save are going to cause 500 errors for users (at least if they happen from admin), it's better to do your validation in your forms code, and write a `Model.clean(..)` method that raises `ValidationErrors` to handle the validation you do in save... (docs: https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.clean related SO https://stackoverflow.com/questions/8771029/raise-a-validation-error-in-a-models-save-method-in-django) – thebjorn Jun 02 '19 at 11:45

1 Answers1

0

The reason why this is a problem is because if you saved already a segment, and now you ask if there is a segment in the database that has the same department or destination. If you did not update the department of destination, then of course there is: that specific segment.

We can exclude the our segment from it, given it exists, with:

from django.db.models import Q

def segment_validate(self, departure_pk, destination_pk, segment_pk):
    segments = self.segments.exclude(pk=segment_pk)
    if not segments.filter(
          Q(departure_id=departure_pk) |
          Q(destination_id=destination_pk)
      ).exists():
        return False
    if segment_pk is None:
        return segments.latest('created').destination_id != departure_pk
    return True

We thus check this with:

assert self.route.segment_validate(self.departure_id, self.destination_id, self.pk), ('Impossible to add the segment, please check the route!')
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555