How to prepopulate ID in Django - python

I have simple question model :
class Question(Polymorph):
text = models.CharField(max_length=256)
poll = models.ForeignKey(Poll)
index = models.IntegerField()
And I would like to prepopulate ( when saving ) index field with ID value. Of course before save I dont have ID value ( its created after it ), so I wonder what is the simplest way to do it ? Any ideas ?
I think about django-signal, but then I will have to call save() method twice.

However you do it, you'll have to call save twice. The ID is generated directly by the database server (except for sqlite, I believe) when the new row is INSERTed, so you'll need to do that in any case.
I would ask if you really need to have the ID value in your index field, though. It's always available as obj.id, after all, so even if you want it as part of a longer value you can always calculate that dynamically via a property.

Yes, trying to figure the next ID out from somewhere wouldn't be portable. Even simpler than using signals is putting something like this in your save method:
def save(self, *args, **kwargs):
"""Save method for Question model"""
if not self.id:
super(Question, self).save(*args, **kwargs)
# Fill the index field
self.index = self.id # plus whatever else you need
return super(Question, self).save(*args, **kwargs)
I guess you'll have to go this way if you can't fully derive the id of the object from the string you get as the input to the filter. But if your string where "something-{id}-something-else" it would be better to get rid of the index field and extract the id value from the string using a regexp and then filter directly by the id with Questions.objects.filter(id=id)

One way to circumvent the double commit is to use something besides an autoincrementing primary key. Django doesn't really care what the primary key is so long as there is one, just one, and it is either a string or an integer. You could use something like a guid, which you can generate on the fly in your models at the time they are initialized, or you can let the database set it for you when you don't care what it is (assuming your database supports guids).

Related

How can a unique timestamp be incremented on constraint violation in a Django model?

As part of using TimescaleDB, which requires a timestamp as the primary key (time in SensorReading), I need to handle the case when the same timestamp is used by different sensor values. One elegant solution might be to smear colliding timestamps (add a microsecond on collision).
How can this problem be solved in a robust and performant manner for the following models?
class Sensor(models.Model):
name = models.CharField(max_length=50)
class SensorReading(models.Model):
time = models.DateTimeField(primary_key=True, default=datetime.now)
sensor = models.ForeignKey(Sensor, on_delete=models.CASCADE)
value = models.FloatField()
P.S. This is a workaround as Django does not support composite primary keys. Otherwise it would be possible to set the sensor and timestamp as a composite primary key.
To work with timescale db, I make a virtual primary key field, that tricks the Django and represents compound key value as a single json tuple.
https://viewflow.medium.com/the-django-compositeforeignkey-field-get-access-to-a-legacy-database-without-altering-db-tables-74abc9868026
You could check the code sample here - https://github.com/viewflow/cookbook/tree/v2/timescale_db
One solution I found was to use a try/except block around the model save call. Try to add a sensor reading, which will succeed most of the time, but if a collision occurs handle that error. Ensure that the exception thrown is exactly because of that collision and then increment the timestamp by one microsecond (the smallest resolution). Then repeat the try to save the sensor reading. This will almost always succeed due to the low collision probability in the first place. Below is the code tested to recursively handle incrementing the timestamp until it succeeds.
class SensorReading(models.Model):
time = models.DateTimeField(primary_key=True, default=datetime.now)
sensor = models.ForeignKey(Sensor, on_delete=models.CASCADE)
value = models.FloatField()
def save(self, *args, **kwargs):
self.save_increment_time_on_duplicate(*args, **kwargs)
def save_increment_time_on_duplicate(self, *args, **kwargs):
try:
super().save(*args, **kwargs)
except IntegrityError as exception:
if all (k in exception.args[0] for k in ("Key","time", "already exists")):
self.time = str(parse_datetime(self.time) + timedelta(microseconds=1))
self.save_increment_time_on_duplicate(*args, **kwargs)
A more robust implementation might also add a max number of tries before aborting.
In general, I'd recommend taking the table management for that sensors table at least somewhat out of Django, do your insertion with psycopg2 or another driver, and create a proper primary key on Sensor, time, then expose the table (or a view on top of it) as a Django model which it can read from, perhaps with a join if you need to. These should be write once, so you shouldn't have to deal with updates which is usually why it needs the non-compound primary key. At some point Django should really start supporting compound primary keys though, it's too bad they don't.
The other approaches may work, but they likely won't scale very well at all. So if you care about ingest rate, I might try something different.

How to query with the ID of the parent instance using **kwargs in Django?

I am currently implementing soft deletion for all models in my database. The idea is that when an instance gets deleted, it actually gets archived with all of its children. If the user tries to create an instance that is identical to the archived one, the archived one gets undeleted along with all of its children instead of creating a new instance.
To do this, I am using django-safedelete where I am making a BaseModel with an overwritten save() method that looks something like this:
def save(self, *args, **kwargs):
# get the foreign key id
foreign_key_id = self.foreign_field.id
# execute a query by that id and some other params
'''I don't know how to do this'''
As to how to do it, I thought I could construct a kwargs dictionary that consists of pairs of <field>:value where <field> = self._meta.get_field(some_field.name) and value = getattr(self, some_field.name).
So how do I add the foreign_key_id to kwargs? I know there is this syntax: Model.objects.filter(foreign_field__id=value)
...but I don't know how to replicate that to put into kwargs the way I'm doing it.
Likewise, is there a better way to do this in general? I don't want to hard-code too many things, which is why I didn't just do this individually for each of the models that I have.
Thank you so much in advance.

Codenerix - Disable a dropdown field with foreign key using ng-readonly

Codenerix
Has anyone knows how to use correctly ng-readonly in a GenModelForm when coming from a sublist tab (GenList) who calls a GenCreateModal windows?
Structure is a master-detail, sublist tab has pk of master table and calls GenCreateModal with this pk argument of master table.
GenCreateModal receives pk argument in his asociated form (the mentioned GenModelForm) and can use it. The goal is to disable field with ng-disabled if pk argument of master table is filled. This way when create from another list of detail table without arguments, field can be filled with a value selecting it with the dropdown, and when coming from master table it cannot be modified and it will be assigned to master pk value.
I tried to do it that way:
First assign 'client' on GenCreateModal with:
def get_initial(self):
client = self.kwargs.get('pk', None)
if client:
self.kwargs['client'] = client
return self.kwargs
Then read it on the GenModelform with:
def __init__(self, *args, **kwargs):
super(DetailForm, self).__init__(*args, **kwargs)
if kwargs.get('initial', None) and kwargs['initial'].get('client', None):
self.fields['client'].widget.attrs[u'ng-readonly'] = 'true'
But it do not work with dropdown fields. Field can be modified.
Cause is that in templatetags_list.py of codenerix we have:
def inireadonly(attrs, i):
field = ngmodel(i)
return addattr(attrs, 'ng-readonly=readonly_{0}'.format(field))
This code set ng-readonly to "true readonly_client" instead of "true" when it comes with value "true" from GenModelForm, values are concatenated.
I found a workaround with:
self.fields['client'].widget.attrs[u'ng-readonly'] = 'true || '
this way the end value will be "true || readonly_client" that result on "true" as desired when evaluated, but I think it is not the proper way.
On my personal fork of django-codenerix I have changed the function to (functions is on two py files, should change both):
def inireadonly(attrs, i):
field = ngmodel(i)
if attrs.get('ng-readonly', None) is None:
attrs = addattr(attrs, 'ng-readonly=readonly_{0}'.format(field))
return attrs
This way it respects the value when it comes filled form GenModelForm, but I'm not sure about inconveniences and collateral effects. For example when want to concatenate conditions, with that change should read old value, concatenate manually and set new value. I think it should be a better way to do it and 'ng-readonly=readonly_{0}'.format(field) should have a functionality that I haven't discovered yet. Don't want to lose it when I discover it. So I revert the change and look for another solution.
Currently I'm using
self.fields['client'].widget.attrs[u'ng-disabled'] = 'true'
and it goes OK, I'm using this way and I have no problem now, but I'm curious about the way to use ng-readonly if I need it on the future. That's because with ng-readonly we can select text on fields with the mouse for example and can not select it with ng-disabled. In some cases it could be of interest.
Has anyone knows how to use ng-readonly in a correct way?
Has anyone knows the functionality of 'ng-readonly=readonly_{0}'.format(field)?
You can define an extra attribute to your fields in your forms. Add {'extra': ['ng-disabled=true']} in the field of your GenModelForm, inside your __groups__ method. Example:
def __groups__(self):
g = [
(_('Info'), 12,
['client', 6, {'extra': ['ng-disabled=true']}],
)
]
return g
You should use ng-disabled as you are doing. This is the way we do it in Django Codenerix Example # Gibhub (lines 41 and 42) and this is how it has been developed for StaticSelects (line 228) as well.

Django - NEVER update a column when saving

I am trying to use citus data (https://www.citusdata.com/) with Django.
Most everything is working so far, except trying to save a model that has already been saved:
NotSupportedError: modifying the partition value of rows is not allowed
This is because django always includes every single field in the update SQL, even if that field has not changed.
In Citus, you must pick a field to be your partitioning field, and then you cannot change it. So, when I'm saving an object, it doesn't like that the partition key is in the update statement, even if it didn't change.
I know that you can pass the update_fields keyword arg to the save method, but I'm wondering if I can somehow tell django to NEVER include a field when updating?
Django does not provide this functionality "out of the box". You could override the save method of your class to set all fields other than your partition field as the value for update_fields
def save(self, **kwargs):
kwargs.setdefault('update_fields', ['field1', 'field2'])
return super(Class, self).save(**kwargs)
A more dynamic option, if you do not want to update this method everytime you change the fields of your class, would be to use the Meta API to get all fields of the class and exclude your partition field
def save(self, **kwargs):
kwargs.setdefault(
'update_fields',
[f.name for f in self.__class__._meta.get_fields() if f.name != 'partition_field']
)
return super(Class, self).save(**kwargs)
There are several other methods by which Django will attempt to update your models. Maybe a base class that all your models inherit from that implement these methods would work

Django - AutoField with regards to a foreign key

I have a model with a unique integer that needs to increment with regards to a foreign key, and the following code is how I currently handle it:
class MyModel(models.Model):
business = models.ForeignKey(Business)
number = models.PositiveIntegerField()
spam = models.CharField(max_length=255)
class Meta:
unique_together = (('number', 'business'),)
def save(self, *args, **kwargs):
if self.pk is None: # New instance's only
try:
highest_number = MyModel.objects.filter(business=self.business).order_by('-number').all()[0].number
self.number = highest_number + 1
except ObjectDoesNotExist: # First MyModel instance
self.number = 1
super(MyModel, self).save(*args, **kwargs)
I have the following questions regarding this:
Multiple people can create MyModel instances for the same business, all over the internet. Is it possible for 2 people creating MyModel instances at the same time, and .count() returns 500 at the same time for both, and then both try to essentially set self.number = 501 at the same time (raising an IntegrityError)? The answer seems like an obvious "yes, it could happen", but I had to ask.
Is there a shortcut, or "Best way" to do this, which I can use (or perhaps a SuperAutoField that handles this)?
I can't just slap a while model_not_saved: try:, except IntegrityError: in, because other restraints in the model could lead to an endless loop, and a disaster worse than Chernobyl (maybe not quite that bad).
You want that constraint at the database level. Otherwise you're going to eventually run into the concurrency problem you discussed. The solution is to wrap the entire operation (read, increment, write) in a transaction.
Why can't you use an AutoField for instead of a PositiveIntegerField?
number = models.AutoField()
However, in this case number is almost certainly going to equal yourmodel.id, so why not just use that?
Edit:
Oh, I see what you want. You want a numberfield that doesn't increment unless there's more than one instance of MyModel.business.
I would still recommend just using the id field if you can, since it's certain to be unique. If you absolutely don't want to do that (maybe you're showing this number to users), then you will need to wrap your save method in a transaction.
You can read more about transactions in the docs:
http://docs.djangoproject.com/en/dev/topics/db/transactions/
If you're just using this to count how many instances of MyModel have a FK to Business, you should do that as a query rather than trying to store a count.

Categories