Django create custom id def save()

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'

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()

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.

Back to Top