I've created the following models:
class BasePrice(models.Model):
start_hour = models.TimeField(blank=False)
end_hour = models.TimeField(blank=False)
monday = models.BooleanField(blank=False)
tuesday = models.BooleanField(blank=False)
wednesday = models.BooleanField(blank=False)
thursday = models.BooleanField(blank=False)
friday = models.BooleanField(blank=False)
price = models.IntegerField(blank=False)
def __unicode__(self):
days = ""
if (self.monday == True):
days = days + " Mon"
if (self.tuesday == True):
days = days + " Tue"
if (self.wednesday == True):
days = days + " Wed"
if (self.thursday == True):
days = days + " Thu"
if (self.friday == True):
days = days + " Fri"
return "Price " + str(self.price) + " at " + str(self.start_hour) + ":" +str(self.end_hour) + " in " + days
def clean(self):
if self.start_hour > self.end_hour:
raise ValidationError('Start hour is older than end hour!')
class RentPeriod(models.Model):
start_date = models.DateField(blank=False)
end_date = models.DateField(blank=False)
desk = models.ForeignKey(Desk)
base_prices = models.ManyToManyField(BasePrice)
def __unicode__(self):
return "Period " + str(self.start_date) + " to " + str(self.end_date) + " for " + self.desk.__unicode__()
def clean(self):
if self.start_date > self.end_date:
raise ValidationError('Start date is older than end date!')
def validate_unique(self, *args, **kwargs):
super(RentPeriod, self).validate_unique(*args, **kwargs)
# overlaping hours
basePrices = self.base_prices.all()
for price in basePrices:
qs = self.__class__._default_manager.filter(
(Q(monday=True) & Q(monday=price.monday)) |
(Q(tuesday=True) & Q(tuesday=price.tuesday)) |
(Q(wednesday=True) & Q(wednesday=price.wednesday)) |
(Q(thursday=True) & Q(thursday=price.thursday)) |
(Q(friday=True) & Q(friday=price.friday)),
start_hour__lte=self.end_hour,
end_hour__gte=self.start_hour
)
if qs.exists():
raise ValidationError({NON_FIELD_ERRORS: ('overlaping hours range',)})
Generally, I'd like to avoid overlaping time in specified days when adding instances of RentPeriod via admin site. When I try to add RentPeriod instance, I get following error:
'' needs to have a value for field "rentperiod" before this many-to-many relationship can be used.
I've read click, but I don't have idea how to get it to work. Could you help?
Attention: complexity (n^2) has no matter in this case.
UPDATE I've created custom validator according to documentation
forms.py
from biurrko.rents.models import RentPeriod
from django.forms.models import ModelForm
class RentPeriodForm(ModelForm):
class Meta:
model = RentPeriod
def clean(self):
cleaned_data = self.cleaned_data
basePrices = cleaned_data['base_prices']
end_hour = cleaned_data['end_hour']
start_hour = cleaned_data['start_hour']
for price in basePrices:
qs = basePrices.filter(
(Q(monday=True) & Q(monday=price.monday)) |
(Q(tuesday=True) & Q(tuesday=price.tuesday)) |
(Q(wednesday=True) & Q(wednesday=price.wednesday)) |
(Q(thursday=True) & Q(thursday=price.thursday)) |
(Q(friday=True) & Q(friday=price.friday)),
start_hour__lte=end_hour,
end_hour__gte=start_hour
)
if qs.exists():
raise ValidationError({NON_FIELD_ERRORS: ('overlaping hours range',)})
# only for test
raise ValidationError({NON_FIELD_ERRORS: ('reached',)})
# return cleaned_data
and admin.py
from django.contrib import admin
from biurrko.rents.models import Desk, Room, RentPeriod, BasePrice
from biurrko.rents.forms import RentPeriodForm
admin.site.register(Desk)
admin.site.register(Room)
admin.site.register(RentPeriod)
admin.site.register(BasePrice)
class RentPeriodAdmin(admin.ModelAdmin):
form = RentPeriodForm
Unfortunately, own validator "is not called" - I mean even test ValidationError is not raising.
UPDATE 2 Figuret out that it was problem with "register" statement. It should be:
admin.site.register(RentPeriod, RentPeriodAdmin)
I would suggest that validate_unique on the model would NOT be the place to do this.
To do this level of validation you need the model to have a PK, but in order to have a PK it needs to be saved to DB, but in order to save to DB it needs to validate. See the problem?
You would need to move this kind of validation into the form/formset. This means overloading the clean() method of the InlineFormset that I assume your using for input of hours. Ref: Inline Form Validation in Django