TabularInline and date range overlap constraint - django

I use the following formset to avoid overlapping date ranges:
class OccupancyInlineFormset(BaseInlineFormSet):
def clean(self):
super().clean()
for form in self.forms:
conflicts = Occupancy.objects.filter(unit=form.cleaned_data['unit'], begin__lte=form.cleaned_data['end'], end__gte=form.cleaned_data['begin'])
if any(conflicts):
raise ValidationError(_('Overlapping occupancies!'), code='overlap')
This works well in principle, but my current Occupancy instance always needs to have an end date of 9999-12-31. When I change this date in the admin form for the current instance (and add a new instance with an ende date of 9999-12-31), the clean() function will always raise an exception based on the values stored in the database. I do not really see how I could avoid this without changing the offending end date (9999-12-31 → 2023-01-31) first in a different (unconstrained) form, which defeats the purpose of the TabularInline form. Thanks for any help!

After thinking the problem through, I arrived at the following (clunky, but working) solution:
def clean(self):
super().clean()
infinity = datetime.date(9999,12,31)
for form in self.forms:
try:
current_id = form.cleaned_data['id'].pk
except AttributeError:
current_id = None
unit = form.cleaned_data['unit']
begin = form.cleaned_data['begin']
end = form.cleaned_data['end']
# Exclude the instance corresponding to the form and the current occupancy (infinity end date) from the set of conflicting instances
conflicts = Occupancy.objects.filter(unit=unit, begin__lte=end, end__gte=begin).exclude(pk=current_id).exclude(end=infinity)
if any(conflicts):
raise forms.ValidationError(_('Overlapping occupancies!'), code='overlap')
# if the new occupancy has an infinity end date ...
elif end == infinity:
try:
current_occupancy = Occupancy.objects.get(unit=unit, end=datetime.date(9999,12,31))
# ... and is not identical with the current occupancy ...
if current_occupancy.pk != current_id:
# ... update the current occupancy to end just before the new (infinite) occupancy
current_occupancy.end = begin - datetime.timedelta(days=1)
current_occupancy.save()
except ObjectDoesNotExist:
pass
This takes into account that an instance may overlap (at least while cleaning the input) with itself, and that a new occupancy with an infinite end date should replace the former "current" occupancy.

Related

Django Save Override Throwing Primary Duplicate Errors

So, I have a model called ScheduleItem
class ScheduleItem(models.Model):
agreement = FK
location = FK
start = models.DateTimeField()
end = models.DateTimeField()
totalHours = DecimalField
def get_total_hours(self):
start = timedelta(hours=self.start.hour, minutes=self.start.minute)
end = timedelta(hours=self.end.hour, minutes=self.end.minute)
td = (end-start).seconds
totalHours=Decimal(td/Decimal(60)/Decimal(60))
return totalHours
def save(self,*args,**kwargs):
if self.pk == None:
super(ScheduleItem,self).save(self,*args,**kwargs)
self.refresh_from_db() # to access the datetime values, rather than unicode POST
self.totalHours = self.get_total_hours()
else:
self.totalHours = self.get_total_hours()
super(ScheduleItem,self).save(self,*args,**kwargs)
This throws PRIMARY key errors. I get duplicate entries with the second super(ScheduleItem,self). I cannot for the life of me figure out how to check for pk to access the datetime value and then save again within the save override method. I've tried moving things around, I've tried saving within the get_total_hours() function, with nothing but trouble.
I just want the object to be committed to the db so I can get the datetime objects and then calculate the total hours.
I'd rather not convert to datetime within the save function.
Does anyone have any tip or can anyone tell me where I'm going wrong?
You should not pass self to save(). You're calling super().save() as a bound method on an instance, so self is implicitly passed as the first argument. Change it to this:
def save(self,*args,**kwargs):
if self.pk is None:
super(ScheduleItem,self).save(*args,**kwargs)
self.refresh_from_db() # to access the datetime values, rather than unicode POST
self.totalHours = self.get_total_hours()
else:
self.totalHours = self.get_total_hours()
super(ScheduleItem,self).save(*args,**kwargs)
You get this weird behaviour because the first positional argument is force_insert, and the model instance evaluates to True. The second call to super().save() tries to force an insert with the same pk you previously saved.

Django - preventing duplicate records

I have a list of client records in my database. Every year, we generate a single work order for each client. Then, for each work order record, the user should be able to create a note that is specific to the work order. However, not all work orders need a note, just some.
Now, I can't simply add a note field to the work order because some times, we need to create the note before the work order is even generated. Sometimes this note is specific to a work order that won't happen for 2-3 years. Thus, the notes and the work order must be independent, although they will "find" each other when they both exist.
OK, so here's the situation. I want the user to be able to fill out a very simple note form, where they have two fields: noteYear and note. Thus, all they do is pick a year, and then write the note. The kicker is that the user should not be able to create two notes for the same year for the same client.
What I'm trying to get as is validating the note by ensuring that there isn't already a note for that year for that client. I'm assuming this would be achieved by a custom is_valid method within the form, but I can't figure out how to go about doing that.
This is what I tried so far (note that I know it's wrong, it doesn't work, but it's my attempt so far):
Note that systemID is my client record
My model:
class su_note(models.Model):
YEAR_CHOICES = (
('2013', 2013),
('2014', 2014),
('2015', 2015),
('2016', 2016),
('2017', 2017),
('2018', 2018),
('2019', 2019),
('2020', 2020),
('2021', 2021),
('2022', 2022),
('2023', 2023),
)
noteYear = models.CharField(choices = YEAR_CHOICES, max_length = 4, verbose_name = 'Relevant Year')
systemID = models.ForeignKey(System, verbose_name = 'System ID')
note = models.TextField(verbose_name = "Note")
def __unicode__(self):
return u'%s | %s | %s' % (self.systemID.systemID, self.noteYear, self.noteType)
And my form:
class SU_Note_Form(ModelForm):
class Meta:
model = su_note
fields = ('noteYear', 'noteType', 'note')
def is_valid(self):
valid = super (SU_Note_Form, self).is_valid()
#If it is not valid, we're done -- send it back to the user to correct errors
if not valid:
return valid
# now to check that there is only one record of SU for the system
sysID = self.cleaned_data['systemID']
sysID = sysID.systemID
snotes = su_note.objects.filter(noteYear = self.cleaned_data['noteYear'])
for s in snotes:
if s.systemID == self.systemID:
self._errors['Validation_Error'] = 'There is already a startup note for this year'
return False
return True
EDIT -- Here's my solution (thanks to janos for sending me in the right direction)
My final form looks like this:
class SU_Note_Form(ModelForm):
class Meta:
model = su_note
fields = ('systemID', 'noteYear', 'noteType', 'note')
def clean(self):
cleaned_data = super(SU_Note_Form, self).clean()
sysID = cleaned_data['systemID']
sysID = sysID.systemID
try:
s = su_note.objects.get(noteYear = cleaned_data['noteYear'], systemID__systemID = sysID)
print(s)
self.errors['noteYear'] = "There is already a note for this year."
except:
pass
return cleaned_data
For anyone else looking at this code, the only confusing part is the line that has: sysID = sysID.systemID. The systemID is actually a field of another model - even though systemID is also a field of this model -- poor design, probably, but it works.
See this page in the Django docs:
https://docs.djangoproject.com/en/dev/ref/forms/validation/
Since your validation logic depends on two fields (the year and the systemID), you need to implement this using a custom cleaning method on the form, for example:
def clean(self):
cleaned_data = super(SU_Note_Form, self).clean()
sysID = cleaned_data['systemID']
sysID = sysID.systemID
try:
su_note.objects.get(noteYear=cleaned_data['noteYear'], systemID=systemID)
raise forms.ValidationError('There is already a startup note for this year')
except su_note.DoesNotExist:
pass
# Always return the full collection of cleaned data.
return cleaned_data

Django using python

1.Kindly tell how can we set the limit for integer fields in django .
2.Kindly tell the codings for the block name (Add Another Choice) should be of limited for the program
class Record(models.Model):
Name = models.CharField(max_length=200,blank=True,null=True,help_text="Employee Name")
Empid = models.CharField(max_length=300,blank=True,help_text="Employee ID")
Salary = models.CharField(max_length=300,blank=True,null=True)
Bonus = models.IntegerField(blank=True,null=True)
class Choice(models.Model):
p=models.ForeignKey(Record)
Month=models.CharField(max_length=200,blank=True,null=True)
Neither can be done at the ORM level; you will need to add database-level constrains using raw SQL.
1 If you just want to check user input use a clean method
ie.
def clean_bonus(self): #thus: clean_FIELDNAME
#check the value and show an error
2 I am not sure what you mean:
a) If you mean you want users to select atleast 1 choice, but limit the maximum use something like this:
class RequireOneFormSet(BaseInlineFormSet):
"""
Require at least one form in the formset to be completed.
"""
def clean(self):
"""Check that at least one form has been completed."""
super(RequireOneFormSet, self).clean()
for error in self.errors:
if error:
return
completed = 0
for cleaned_data in self.cleaned_data:
# form has data and we aren't deleting it.
if cleaned_data and not cleaned_data.get('DELETE', False):
completed += 1
if completed < 1:
raise forms.ValidationError("At least one %s is required." %
self.model._meta.object_name.lower())
b) If you mean you want to have multiple foreign keys use django ContentTypes

django - issue with get_or_create - results in two objects created in DB

I have a table in my postgresql db holding a state of an hour record. For each month, project and user I need exactly one state.
I'm using the get_or_create method to either create a "state" or to retrieve it if it already exists.
HourRecordState.objects.get_or_create(user=request.user, project=project, month=month, year=year, defaults={'state': 0, 'modified_by': request.user})
After running this for about two years without problems I stumbled over the problem that I have one HourRecordState twice in my database. Now each time the get_or_create method is called it throws the following error:
MultipleObjectsReturned: get() returned more than one HourRecordState
-- it returned 2
I'm wondering how it could happen that I have two identical records in my DB. Interestingly they have been created at the same time (seconds, not checked the milliseconds).
I checked my code and I have in the whole project only one get_or_create method to create this object. No other create methods in the code.
Would be great to get a hint..
Update:
The objects have been created at almost the same time:
First object: 2011-10-04 11:04:35.491114+02
Second object: 2011-10-04 11:04:35.540002+02
And the code:
try:
project_id_param = int(project_id_param)
project = get_object_or_404(Project.objects, pk=project_id_param)
#check activity status of project
try:
is_active_param = project.projectclassification.is_active
except:
is_active_param = 0
if is_active_param == True:
is_active_param = 1
else:
is_active_param = 0
#show the jqgrid table and the hour record state form
sub_show_hr_flag = True
if project is not None:
hour_record_state, created = HourRecordState.objects.get_or_create(user=request.user, project=project, month=month, year=year, defaults={'state': 0, 'modified_by': request.user})
state = hour_record_state.state
manage_hour_record_state_form = ManageHourRecordsStateForm(instance=hour_record_state)
if not project_id_param is False:
work_place_query= ProjectWorkPlace.objects.filter(project=project_id_param, is_active=True)
else:
work_place_query = ProjectWorkPlace.objects.none()
work_place_dropdown = JQGridDropdownSerializer().get_dropdown_value_list_workplace(work_place_query)
except Exception, e:
project_id_param = False
project = None
request.user.message_set.create(message='Chosen project could not be found.')
return HttpResponseRedirect(reverse('my_projects'))
Well this is not an exact answer to your question, but I think you should change your database scheme and switch to using UNIQUE constraints that help you to maintain data integrity, as the uniqueness will be enforced on database level.
If you state that for every month, user and project you need exactly one state, your model should look something like this (using the unique_together constraint):
class HourRecordState(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project)
month = models.IntegerField()
year = models.IntegerField()
# other fields...
class Meta:
unique_together = ((user, project, month, year),)
Because get_or_create is handled by django as a get and create multiple processes seem to be able under certain condition to create the same object twice, but if you use unique_together an exception will be thrown if the attempt is made....

django - weird results (cached?) obtained while storing calculated values in fields at model level

Dear django gurus,
please grab my nose and stick it in where my silly mistake glows.
I was about to proceed some simple math operation based on existing field values and store it in a separate field (subtotal) of current instance.
My question is actually included in the very last comment of code.
class Element(models.Model):
name = models.CharField(max_length=128)
kids = models.ManyToManyField('self', null=True, blank=True, symmetrical=False)
price = models.IntegerField('unit price', null=True, blank=True)
amount = models.IntegerField('amount', null=True, blank=True)
subtotal = models.IntegerField(null=True, blank=True)
def counter(self):
output = 0
# Check if this is the lowest hierarchy level (where the intention is to
# store both values for price and amount) and proceed calculations
# based on input values. Also check if values are set to avoid operations
# with incompatible types.
# Else aggregate Sum() on subtotal values of all kids.
if self.kids.count() == 0:
if self.price == None or self.amount == None:
output = 0
else:
output = self.price * self.amount
else:
output = self.kids.aggregate(Sum('subtotal'))['subtotal__sum']
self.subtotal = output
def __unicode__(self):
return self.name
This is how my sample data look like (I am sorry if I am missing some convention of how to show it).
element.name = son
element.kids = # None
element.price = 100
element.amount = 1
element.subtotal = 100 # Calculates and stores correct value.
element.name = daughter
element.kids = # None
element.price = 200
element.amount = 5
element.subtotal = 1000 # Calculates and stores correct value.
element.name = father
element.kids = son, daughter
element.price = # None. Use this field for overriding the calculated
# price at this level.
element.amount = # None.
element.subtotal = 25 # Calculates completely irrelevant value.
# The result seems to be some previous state
# (first maybe) of subtotal of one of the kids.
# This is where my cache part of question comes from.
While solving this simple task I have started with clean() class, but the results were calculated after second save only (maybe a good topic for another question). I switched then to custom method. But now after a night spent on this I would admiteddly use Mr. Owl's words to Winnie the Pooh: "You sir are stuck!". At this point I am using only native django admin forms. Any help will be most appreciated.
Without seeing the code you are using to invoke all these calculations, I can only guess what the problem might be. It may be helpful to show your view code (or other), which 'kicks off' the calculations of subtotal.
There are two potential issues I can see here.
The first is in your counter method. Are you saving the model instance after calculating the total?
def counter(self):
output = 0
if self.kids.count() == 0:
if self.price == None or self.amount == None:
output = 0
else:
output = self.price * self.amount
else:
output = self.kids.aggregate(Sum('subtotal'))['subtotal__sum']
self.subtotal = output
self.save() # save the calculation
Without saving the instance, when you query the children from the parent, you will get the current database value rather than the newly calculated value. This may or may not be an issue, depending on the code that calls counter to begin with.
The other potential issue I see here is cache invalidation. If a child updates their price or amount, is the parent notified so it can also update its subtotal? You may be able to override the save method to do your calculations at the last minute.
def save(self, *args, **kwargs):
self.counter() # calculate subtotal
super(Element, self).save(*args, **kwargs) # Call the "real" save() method.
for parent in self.element_set.all(): # use a related name instead
parent.save() # force recalculation of each parent
You'll note that this will only force the correct values of subtotal to be valid after saving only. Be aware that overriding the save method in this way is directly contradictory to my first solution of saving the instance when calculating counter. If you use both, together, you will get a StackOverflow as counter is calculated then saved, then during saving counter is calculated, which will trigger another save. Bad news.