I have this postgres function, What it does is when I save an item, it creates an cl_itemid test2021-20221-1. then in table clearing_office the column office_serial increments 1 so next item saved will be test2021-20221-2
SELECT CONCAT(f_office_id,f_sy,f_sem,'-',office_serial) INTO office_lastnumber from curriculum.clearing_office
where office_id=f_office_id;
UPDATE curriculum.clearing_office SET office_serial =office_serial 1 where office_id=f_office_id;
{
"studid": "4321-4321",
"office": "test",
"sem": "1",
"sy": "2021-2022",
}
Is it possible to create this through Django models or perhaps maybe an alternative solution for this problem?
This is my model class
class Item(models.Model):
cl_itemid = models.CharField(primary_key=True, max_length=20)
studid = models.CharField(max_length=9, blank=True, null=True)
office = models.ForeignKey('ClearingOffice', models.DO_NOTHING, blank=True, null=True)
sem = models.CharField(max_length=1, blank=True, null=True)
sy = models.CharField(max_length=9, blank=True, null=True)
class Meta:
managed = False
db_table = 'item'
CodePudding user response:
I do this in alot of my Models, tad different. I just use Forms and tac on a custom save method
I haven't worked with unmanaged models, but from what I can see from google it's not different
Form (forms.py)
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = (
'cl_itemid',
'studid',
'office',
'sem',
'sy',
)
def __init__(self, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
obj = super(ItemForm, self).save(commit=False)
if not self.instance:
# you only want it hitting this if it's new!
from datetime import datetime
now = datetime.now()
count = Item.objects.all().count()
# you could also just grab the last
# chop the id to get the last number
obj.cl_itemid = 'test{0}-{1}'.format(now.strftime('%Y-%m%d%y'), count)
if commit:
obj.save()
You might even be able to tac it BEFORE the actual save method super(ItemForm, self).save(commit=False)
or even do a cl_itemid
..i'll do some testing on my end to see if that could be an option, might be much cleaner
Usage
View (POST)
def ItemFormView(request):
from app.forms import ItemForm
if request.method == "POST":
# this is just for edits
itemObj = Item.objects.get(pk=request.POST.get('itemid')) if request.POST.get('itemid') else None
form = ItemForm(request.POST or None, instance=itemObj)
if form.is_valid():
obj = form.save()
return HttpResponseRedirect(reverse('sucesspage')
print(form.errors)
print(form.errors.as_data())
# this will render the page with the errors
data = {'form': form}
else:
data = {'form': ItemForm()}
return render(request, 'itemform.html', data)
Shell
f = ItemForm(data={'office':'test', 'sem':'1', 'etc':'etc'})
if f.is_valid():
o = f.save()
CodePudding user response:
One simple way might be to use a default
function in python.
def get_default_id():
last_id = Item.objects.last().cl_itemid
split_id = last_id.split('-')
split_id[-1] = str(int(split_id[-1]) 1)
new_id = '-'.join(split_id)
return new_id
class Item(models.Model):
cl_itemid = models.CharField(..., default=get_default_id)
Similar approach described in this answer.
One thing you will need to anticipate with this approach is that there might be a race condition when two or more new objects are created simultaneously (i.e., the default function runs at the same time for two new objects, potentially resulting in the same ID being returned for the two new objects). You can work around that potential issue with transactions or retries if you think it will be a problem in your application.
A robust approach would be to create your own model field and handle the implementation internally in the field. That would allow you to implement the solution in different ways, depending on the DB dialect and have it work across DB implementations without the race condition issue.