Best Practices for updating one field based on another

Hello I am new to django and wondering what the best practices are for a model I am designing.

I have a field, tn, which needs to increase by one every time a new instance of that class is created. I also have another field, Counter, which needs to increase by 1 every n times tn increases. What is the best practice here? Is it better to have Counter as a separate class or a field within tn?

Depending on which is better how would you go about doing that? Any help is greatly appreciated.

class Tn(models.model):
    tn = models.PositiveIntegerField(unique=True, primary_key=True, editable=False)
    counter = models.PositiveIntegerField(editable=False

)

I’m a bit confused.
What are you trying to accomplish?

But if i understood correctly
The tn field should be a AutoField or BigAutoField, this documentation covers them.
But the counter field is always to be the same as the maximum number of tn, if that’s the case, then this should not be a field on the database, but a computed valued. See aggregation and the section Cheat Sheet contains a example using the Max function

Hello thank you for your quick reply, I think I understand about the TN field - I have read that and changed it to:

tn = models.AutoField(primary_key=True)

I want the Counter field to increase every time TN is divisible by 40 without remainder or every 40 tn.
So for example:
TN 1-40, Counter 1
TN 41-80, Counter 2
TN 81-120, Counter 3
etc.
Does this make more sense what I am trying to achieve?

Since this can be computed quickly from tn, I would not make it a separate field. I would compute it on the fly as necessary.

Side note: In a production environment, you may end up with “gaps” in your sequence. There is no guarantee that you will have an instance of your model for every distinct value of tn. You need to decide how important that factor is, and how that affects your Counter.

Hi Ken, thanks for your reply.

How would I end up with gaps? with tn defined as this:

tn = models.AutoField(primary_key=True)

Will the tn not just automatically increase.
How would I compute it on the fly? I have this in my models.py file not sure if this is the right way to go:

counter_number = models.PositiveIntegerField(editable=False)

@property
def _counter_number(self, request): 
    if type(self.tn/40) == int:
        self.counter_number += 1
        return self.counter_number
   
def save(self, *args, **kwargs):
    self.counter_number = self._counter_number
    super().save(*args, **kwargs)

Because in a production environment, you will have multiple processes running. Process A may get value 345 to assign while process B gets 346. Now, if process A fails for whatever reason, you will not have a 345 in the database. Process C is not going to go back to fill the gap, it’s going to continue on with 347.

How likely this is to happen will depend upon the system load and the reliability of the systems being run (host, database, network between them, network between the server and the user, etc, etc, etc.)

Side note: I’d use if self.tn % 40 == 0: return self.tn//40.

Hi Ken,

I see - that’s fine, counter doesn’t need to be for EXACTLY 40 a few errors will be ok, also thinking about the number of users at once etc I don’t think it will be a problem. What will happen if an entry of TN gets deleted? Will the Counter value persist?

@property
def _counter_number(self):
if self.tn % 40 == 0:
return self.tn//40

def save(self, *args, **kwargs):
    self.net_weight = self._net_weight_calculator
    self.counter_number = self._counter_number
    super().save(*args, **kwargs)

Few questions:

  • Is it correct to definre this as a method of the class in the models.py file?
  • what is the purpose of the *args **kwargs and are they neccessary?
    Thanks again

Hi Ken, I have tried this above and in my admin.py file I get the following error:

Exception Type: FieldError at /admin/stocks/weighbridge/add/
Exception Value: Unknown field(s) (counter_number) specified for Weighbridge. Check fields/fieldsets/exclude attributes of class WeighbridgeAdmin.

I also get a similar error for self.net_weight. How do I use the results of the _counter_number property? as a field?

Any help is greatly appreciated.

I would need to see what you currently have as the complete class, along with your ModelAdmin class to see how it’s being used.

Hi Ken,

This is what I’ve got but not sure it is best practices, and it works with the lines commented out but when I try to use them I get a different type of error.

#models.py
class InloadBook(models.Model):
    tn = models.OneToOneField(Weighbridge, primary_key=True, on_delete=models.PROTECT)
    
    counter_number = models.IntegerField(verbose_name='Counter Number', default=0)
    
    def save(self, *args, **kwargs):
        #if self.tn % 40 == 0: 
        #    self.counter_number = self.tn//40
        #else:
        
        self.counter_number = 1 
        super(InloadBook, self).save(*args, **kwargs)
#admin.py
from .models import InloadBook
admin.site.register(InloadBook)

With those lines uncommeted I get this error:

 File "C:\Users\georg\onedrive\storeapp\demo0922\stocks\models.py", line 173, in save
    if self.tn % 40 == 0:
       ^^^^^^^^^^^^

Exception Type: TypeError at /admin/stocks/inloadbook/add/
Exception Value: unsupported operand type(s) for %: 'Weighbridge' and 'int'

I get I probably need some sort of clause for the data types but not sure the best way to go about this rather than fudging something that isn’t ideal I’d like to learn how to do it the best way.

Does this make sense?

Correct, because tn is not a value in Inloadbook. Here, tn is an instance of Weightbridge. (In your earlier example, tn was a value, an AutoField - I don’t understand what you’re trying to do here.)
What does your Weightbridge model look like?

It may even be better if you just tried to explain a little bit of the background and what you’re trying to model with this.

Hey Ken, yes I have moved my Counter field to another class as it makes more sense with the data. The overall picture is I am trying to develop a Weighbridge system that has tickets for when a delivery is made and every 40 loads that are a delivery not a collection (i.e. Inload) need to be grouped together using the Counter field. so the tn is the primary key on the weighbridge class and also the primary key on the inload in a onetoone relationship. the weighbridge class looks like this:

#models.py
class Weighbridge(models.Model):
    weighdate = models.DateTimeField(default=now)
    tn = models.AutoField(primary_key=True) 

    PAYMENT_CHOICE = [( "PO",'£'), ('CA', "CASH"), ("AC",'ACC'), ('CH', 'CHEQUE')]
    payment = models.CharField(choices=PAYMENT_CHOICE, max_length=20, default='AC')
    quality_control = models.BooleanField(default=True, verbose_name='The Vehicle/Trailer has been inspected and is clean & ready for Loading/Tipping') #REMOVE THE DEFAULT

    supplier = models.ForeignKey(Supplier,on_delete=models.PROTECT)
    variety = models.ManyToManyField(to=Variety, related_name='weighbridge', blank=True) 
    loading_statement = models.BooleanField(default=True, verbose_name='Loading / Unloading Equipment Clean and Ready for Use.' )

#etc...

and inload book as above.

So if x is an instance of Weightbridge, how would you access any attribute of x? (For example, how would you access the weighdate attribute of x?)

in the admin.py file? this is the code I have for that:


class ReferencesInline(admin.TabularInline):
    model = Reference
    extra = 3

class LastLoadInline(admin.TabularInline):
    model = LastLoad
    extra = 3
    max_num = 3

class WeighbridgeAdmin(admin.ModelAdmin):
    
    
    readonly_fields = ['tn', 'weighdate', 'net_weight']
    inlines = [ReferencesInline, LastLoadInline]
    fieldsets = [
        ('Load Details', {'fields':[('tn','weighdate', 'payment','supplier'), 'quality_control',  'loading_statement' ,'variety' ]}),
        ('Delivery/Collection Details', {'fields':['store_location', 'external_location', ('weight_one', 'weight_two', 'net_weight')],  'classes':('wide', 'extrapretty')},), # 
        ('TASCC CODE OF PRACTICE CONDITIONS APPLY', {'fields':[]}),
        ('Haulage', {'fields':[('tr_no',  'registration','comments'), ('weighoperator_name', 'driver_name')]})
    ]
    
    list_display = ('weighdate', 'supplier', 'net_weight', 'store_location', 'external_location')
    
    #'net_weight')
    list_filter = ['tn','supplier', 'store_location', 'variety']
    search_fields = ['supplier', 'variety']
    date_hierarchy = 'weighdate' 

I cut some fields from the last cut and paste as its a bit long. But the idea is the same. Does this answer your question?

No it doesn’t - I’m asking a very specific and limited question.

If you have the query:
a_weigh_bridge = Weighbridge.objects.get(pk=10)
then a_weigh_bridge is an instance of Weighbridge.

What one line of code can you write that would print the value of the weighdate attribute of a_weigh_bridge?

I am really not sure but would it be something like:
print(a_weigh_bridge.weighdate())

Close, but not a function. a_weigh_bridge.weighdate.

If you’re not comfortable with objects and object attribute access, you may wish to review 9. Classes — Python 3.11.3 documentation and the work that you did with Writing your first Django app, part 1 | Django documentation | Django

Anyway, with this in mind, if self.tn is an instance of Weighbridge, how would you then access the tn attribute of that object? (Again, looking for one statement.)

ok thank you - I have read both of these before (the second link multiple times) but theory and practice don’t always marry up properly (Sorry!).

Weighbridge.objects.get(pk=self.tn).tn

If this is correct - why do we need to do this and not just use self.tn?

Thank you Ken, sorry for my complete misunderstanding here…
Georgia

Nope, let’s take another step back.

self.tn is the reference to the Weighbridge object. You do not need to execute another query.

self.tn.tn is then the reference to the tn attribute of that Weighbridge object.

ok so if I wanted the supplier for that same tn would it be self.tn.supplier ? and we can use self.tn to reference the specific Weighbridge object because it is the primary key?