I am using flask-mongoengine and think I am running in some kind of race conditions while trying to overwrite the Document.save method.
My models (simplified) look like this:
class User(Document):
meta = {"collection": "users"}
name = StringField()
class Group(Document):
meta = {"collection": "groups"}
name = StringField()
class History(EmbeddedDocument):
key = StringField()
oldValue = StringField()
newValue = StringField()
class Asset(DynamicDocument):
meta = {"collection": "assets"}
c_id = SequenceField()
name = StringField()
history = ListField(EmbeddedDocumentField(History))
user = ReferenceField('User')
group = ReferenceField('Group', required=True, default=Group.objects.first())
def save(self, **kwargs):
for key, value in self._data.items():
history_update = History(
key=key,
oldValue="",
newValue=str(value)
)
self.history.append(history_update)
return super(Asset, self).save(**kwargs)
What I am trying to achieve is:
When a new Document of type Asset is created, add an entry of type History for each Key/Value pair of the document that changed. (Here from None to some value, I have similar code in the update method for changes on existing assets). This history list should be something like a changelog of the particular asset through its lifetime.
My problem with the current implementation is that:
c_id of type SequenceField is None in my for-loop.
str(value) for the User object gives me the correct user-object (or the result of my custom __str__ method) but str(value) for the Group object gives me DBRef('groups', '<mongoidstring>') and does not trigger my customer str method
When debugging with a breakpoint beforehand, these two errors do not occur. c_id has its correct value and my group object is a group object and not a DBRef object
I've tried saving the Document once before and then adding my history which at least gives me a correct c_id but the group is still a DBRef.
I do think the SequenceField is populated in parallel and therefore still None when I try to access it but not when I come through the debugger. But the DBRef still gives me headaches. And that I don't really see a way to properly implement my ChangeHistory through overwriting the save method. Any ideas how to properly handle this?
So I find an answer myself (somewhat).
SequenceFields are only populated during a save(). When overwritting the save method we first have to make a super.save to get the SequenceField value or we have to assume its value by the helper collection that is created by mongoengine. I took the easy route and just added an super(Asset, self).save() and the c_id is set corectly for the changes afterwards.
ReferenceFields are avalaible as DBRef until you first access it from the Document object.
Just add some kind of check beforehand to ensure its value is correctly resolved like:
assert self.group is not None
assert self.user is not None
Related
I have am in the following situation. I have a Django model that includes a link to another class, ex:
class Document(models.Model):
document = models.FileField(upload_to=user_file_path)
origin = models.CharField(max_length=9)
date = models.DateField()
company = models.ForeignKey(Company, on_delete=models.CASCADE)
Company includes a property called status.
In another part of the code I am working with a doc of type Document and I want to update the status property of the company within the doc, so I proceed to:
doc.company.status = new_value
doc.save()
or
setattr(company, 'status', new_value)
company.save()
In the first case, the value of doc.company.status is updated, but if I query the company it keeps the old value, and in the second case is the other way around, the value of company.status is updated but doc.company.status keeps the old value.
I had always assumed that updating either (doc.company or company) of them had an immediate effect over the other, but now it seems that doc has a copy of company (or some sort of lazy link difficult to foresee) and both remain separate, and both of them have to be updated to change the value.
An alternative that seems to work is (saving doc.company instead of doc):
doc.company.status = new_value
doc.company.save()
The new value for status does not seem to propagate instantly to company, but yes when it is required by a query or operation.
May someone explain the exact relationship or way of doing or provide a reference where I may find the proper explanations?
Thanks
Setting an object instance attribute changes only the attribute on that instance. After using save(), the value is saved in the database.
If there were previously defined variables of the same instance, they will remain the same. That is, unless you call refresh_from_db() method.
Consider the following scenario:
company_var1 = Company.objects.get(pk=13)
company_var2 = Company.objects.get(pk=13)
company_var1.status = new_value # This only changes the attribute on the model instance
print(company_var1.status) # Output: new value
print(company_var2.status) # Output: old value
company_var1.save() # This updates the database table
print(company_var1.status) # Output: new value
# Note how the company_var2 is still the same even though
# it refers to the same DB table row (pk=13).
print(company_var2.status) # Output: old value
# After doing this company_var2 will get the values from the DB
company_var2.refresh_from_db()
print(company_var1.status) # Output: new value
print(company_var2.status) # Output: new value
I hope that clears things up and you can apply it to your case.
I am developing an app in Django.
I wanted to insert in my model an auto-incrementing alphanumerical unique ID field, having, by default, a fixed alphabetical part and an auto-incrementing numerical part. But I also want the availability to change, from admin section, this id to another alphanumerical one, with a different alphanumerical and numerical part.
I tryed to implement this, but it turned out that trying to implement such a field and making it the autofield of the model generates problems in my database.
So I am changing my aim: now I want to implement a time-based alphanumerical unique field with predefined aplphabetic part. Please note: I don't want to overwrite the django default id field, I just want to include in my model a field that gets as default value a unique customized alphanumerical value.
Here is what I did, in my models.py:
def return_timestamped_id():
prefix = "ITCH"
import time
this_time = time.time()
this_time = this_time *10000000
this_time = int(this_time)
timestamp = str(this_time)
default_value = prefix + timestamp
return(default_value)
class object_example(models.Model):
name = models.CharField(max_length=256, blank=True, null=True)
Id_generated = models.CharField(max_length=256, blank=False, null=False, unique=True, default=return_timestamped_id())
The problem is that, as I add objects to this model from the admin section, The
Id_generated is always the same.
I expected that the return_timestamped_id() function was called every time I add a new object. It is clear instead that is called just once and then the same return value is passed to the Id_generated of every new object.
How can I change my code in order to get a different timestamp every time a new object is added?
As you probably saw in the Django docs, you can use either a value or a callable as a default. If you use a callable (e.g. a function) then it will be called each time a default is needed.
The problem: you were passing a value because you were calling your function default=return_timestamped_id(). The function was being called once, when your module (models.py) was imported into the application.
The solution: pass the function itself default=return_timestamped_id
You can see in the django.models.Fields class the relevant code (comments mine):
class Field():
def __init__(self, ..., default=NOT_PROVIDED,...):
...
self.default = default # save the default as a member variable
...
def get_default(self):
"""Return the default value for this field."""
return self._get_default()
#cached_property
def _get_default(self):
if self.has_default():
if callable(self.default): # if it is callable, return it
return self.default
return lambda: self.default # else wrap in a callable
I have a field in my collection that I would like to mask for responses. Here is a code example of what I need to achieve:
class Entity(ndb.Model):
name = ndb.StringField()
#property
def name(self):
return self.name [:2] + "***"
Expected result: name in database: John, name returned with API: Jo***
When I'm trying with code above getting
TypeError: Cannot set non-property name when trying to create an Entity
Is there any smarter way to do it than masking name on every response?
Is it possible to modify values in _pre_get_hook() just for a response without changing a field in the database?
I think what you need is _post_get_hook, something along the lines of
def _post_get_hook(self,key,future):
self.name = self.name[:2]+"***"
The fact that the name is modified is worrisome if a put is done on the same entity. I am not sure how your response is being constructed. If there is a way to filter some of the fields, then you can have a separate property that is derived based on the name in the _post_get_hook which is safer.
I have a Model you see bellow:
class PriceModel(models.Model):
name = models.CharField(max_length=12)
the PriceModel's id is increased from 1. I want to add other field, which is related to the id. Such as I want it to #PM1, #PM2... means the #PM + id.
How to create the related field?
I want to add other field, which is related to the id. Such as I want it to #PM1, #PM2... means the #PM + id.
First of all, it is not guaranteed that for all database systems, the id is always incremented with one. For example if the database works with transactions, or other mechanisms, it is possible that a transaction is rolled back, and hence the corresponding id is never taken.
If you however want some "field" that always depends on the value of a field (here id), then I think a #property is probably a better idea:
class PriceModel(models.Model):
name = models.CharField(max_length=12)
#property
def other_field(self):
return '#PM{}'.format(self.id)
So now if we have some PriceModel, then some_pricemodel.other_field will return a '#PM123' given (id is 123). The nice thing is that if the id is changed, then the property will change as well, since it is each time calculated based on the fields.
A potential problem is that we can not make queries with this property, since the database does not know such property exists. We can however define an annotation:
from django.db.models import Value
from django.db.models.functions import Concat
PriceModel.objects.annotate(
other_field=Concat(Value('#PM'), 'id')
).filter(
other_field__icontains='pm1'
)
I think you can in your model serializer add a special field, rather than add a field in the Model.
class PriceModelSerializer(ModelSerializer):
....
show_id = serializers.SerializerMethodField(read_only=True)
class Meta:
model = PriceModel
def get_show_id(self):
return '#PM' + self.id
Hello,
I have bound a ModelForm to one of my model that contains a ForeignKey to another model everything driven by a CreateView. What I want to achieve is to create the model object corresponding to the foreign key if it doesn't exist before the form is overall validated and the final object created in database.
Below the models I use:
class UmsAlerting(models.Model):
alert_id = models.IntegerField(primary_key=True, editable=False)
appli = models.ForeignKey('UmsApplication')
env = models.ForeignKey('UmsEnvironment')
contact = models.ForeignKey('UmsContacts')
custom_rule = models.ForeignKey('UmsCustomRules', null=True, blank=True)
class UmsApplication(models.Model):
appli_id = models.IntegerField(primary_key=True)
trigram_ums = models.CharField(max_length=4L)
class UmsContacts(models.Model):
contact_id = models.IntegerField(primary_key=True)
mail_addr = models.CharField(max_length=100L)
class UmsEnvironment(models.Model):
env_id = models.IntegerField(primary_key=True)
env_name = models.CharField(max_length=5L)
The model bound to the form is UmsAlerting. The model object I want to create if it doesn't exist is UmsContacts. I managed to use the field's clean method in my ModelForm of the contact field and use the get_or_create method like below:
def clean_contact(self):
data = self.cleaned_data['contact']
c, _ = UmsContacts.objects.get_or_create(mail_addr=data)
return c
It perfectly works when the contact is already in the database but when it needs to be created my form return a ValidationError on the contact field saying "This field cannot be null". If I submit the same form a second time without changing anything the UmsAlerting object is well created with no validation error.
My guess is that, for a reason I don't get, when get_or_create is used to create a UmsContacts object it cannot be used to create the new UmsAlerting object. So in clean_contact method the get is working and returns the UmsContacts object but the create part doesn't. It'd be like the UmsContacts object is saved when the whole form is validated but not before as I'd want it to.
Anyone could help me find out what is the problem ? Is using the clean method not the best idea ? Is there another strategy to use to take around this problem ?
Thanks in advance for your help.
It's probably because the object you are creating expects value for contact_id. If you use contact_id field for just setting object id -then you do not have to create it at all. Django takes care of Id's automatically.
Also. field clean method should return cleaned data not object. That creates whole lot more problems on its own.