Avoid non-specified attributes on model creation (Django + MongoDB) - python

I want to create an object in Python but, since I'm using Django, I don't want to save those attributes with None or null value.
When I create an object passing a couple of arguments, those attributes passed are assigned correctly; but the rest, which I wish were not created, are assigned the value "None"
models.py:
from djongo import models
class MyClass(models.Model):
_id = models.CharField(max_length=20, primary_key = True)
attr1 = models.TextField()
attr2 = models.CharField(max_length=300)
attr3 = models.IntegerField()
attr4 = models.CharField(max_length=50)
views.py:
def index(request):
a = MyClass( _id = 'TestID', attr1 = 'Xxxx' )
for key in a:
print(str(key) + " = " + str(a[key]))
a.save()
What I get from the print loop is:
_id = TestID
attr1 = Xxxx
attr2 = None
attr3 = None
attr4 = None
Which means I am saving an object full of empty data.
I would like to save a MyClass object with just the specified attributes in order to save space in memory.
EDIT:
I changed the code and only used djongo. Before I was using mongoengine's Document structure, but not anymore.
Also, I have checked what's written in the MongoDB database and it saves this:
{
"_id" : "TestAptm_2", #Type String
"access" : "", #Type String
"address" : "", #Type String
"capacity" : null, #Type null
"city" : "" #Type String
}
Those empty and null values is what I want to avoid.

As django models are the bridge between your complex python objects and your database, which makes up the django ORM system and is an essential part of your database, you simply must not design your fields dynamically. This is same as having a database table with different number of columns per row, which is impossible. Instead of doing that, you can create separate models for each type of object of your and store them in your database that way.

You are printing the attributes of the python object which doesn't necessarily reflects what got saved in the database.
To check the the shape of the raw object in the database, you can use as_pymongo() method.
Mongoengine is actually behaving as you want, see below:
class MyClass(Document):
_id = StringField(max_length=20, primary_key = True)
atr1 = StringField()
atr2 = StringField(max_length=300)
atr5 = StringField(max_length=50)
atr6 = IntField()
MyClass(_id='whatever', atr1='garbage').save()
print(MyClass.objects.as_pymongo())
# [{u'atr1': u'garbage', u'_id': u'whatever'}]

Related

Combine Multiple Fields in QuerySelectField with Lambda & NoneTypes

Utilizing Flask, SQLAlchemy, WTForms and WTForms-SqlAlchemy to leverage the QuerySelectField type.
mdm_forms.py:
def readiness_level_details():
return ReadinessLevelDetail.query.order_by('readiness_level_type_id')
class ReadinessLevelAssessmentForm(FlaskForm):
activity = QuerySelectField('Activity', validators=[DataRequired()], allow_blank=False, query_factory=activities, get_label='name')
readiness_level_detail = QuerySelectField('RL', validators=[DataRequired()], allow_blank=False, query_factory=readiness_level_details, get_label=lambda rld: f"{'' if rld.readiness_level_category.name is None else rld.readiness_level_category.name}" )
assessment_date = DateField('Assessment Date', validators=[DataRequired()])
justification = TextAreaField('Justification')
assessor = QuerySelectField('Assessor', allow_blank=True, query_factory=users, get_label='email')
submit = SubmitField('Save')
Specifics from Model.py
class ReadinessLevelDetail(BaseModel):
__tablename__ = 'readiness_level_detail'
readiness_level_category_id = db.Column(db.Integer, db.ForeignKey('readiness_level_category.id'))
# Direct Model Relationships
readiness_level_category = relationship("ReadinessLevelCategory", back_populates="readiness_level_details")
class ReadinessLevelCategory(BaseModel):
__tablename__ = 'readiness_level_category'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
abbreviation = db.Column(db.String(255))
description = db.Column(db.Text)
sort_order = db.Column(db.Integer)
# Indirect Model Relationships
readiness_level_details = relationship("ReadinessLevelDetail", back_populates="readiness_level_category")
My specific problem is coming from this line:
readiness_level_detail = QuerySelectField('RL', validators=[DataRequired()], allow_blank=False, query_factory=readiness_level_details, get_label=lambda rld: f"{'' if rld.readiness_level_category.name is None else rld.readiness_level_category.name}" )
Specifically:
get_label=lambda rld: f"{'' if rld.readiness_level_category.name is None else rld.readiness_level_category.name}"
I get an error regardless of how I write this lambda. There will always be a NoneType associated with the Category. I simplified the f string but it will eventually use what is displayed along with several other values to concatenate a string for the label on the drop down.
AttributeError: 'NoneType' object has no attribute 'name'
Specific section from the documentation states this:
Specify get_label to customize the label associated with each option.
If a string, this is the name of an attribute on the model object to
use as the label text. If a one-argument callable, this callable will
be passed model instance and expected to return the label text.
Otherwise, the model object’s str will be used.
I have plenty of other fields built with the following format, but there is no allowance for a NoneType:
get_label=lambda s: '%s (%s %s)' % (s.team.name, s.organization_office.organization.abbreviation, s.organization_office.office.name))
Does anyone have any ideas on how I can pull multiple columns from related tables with some values being NoneType and build the label? I basically need to check for NoneType and have been unsuccessful inside of the lambda here.
Well, this was me being dumb. Can't check an attribute named "name" on a NoneType object. Face palm for the day.

How to override create_table() in peewee to create extra history?

Following this SO answer and using the (excellent) Peewee-ORM I'm trying to make a versioned database in which a history of a record is stored in a second _history table. So when I create a new using the create_table() method I also need to create a second table with four extra fields.
So let's say I've got the following table:
class User(db.Model):
created = DateTimeField(default=datetime.utcnow)
name = TextField()
address = TextField()
When this table is created I also want to create the following table:
class UserHistory(db.Model):
created = DateTimeField() # Note this shouldn't contain a default anymore because the value is taken from the original User table
name = TextField()
address = TextField()
# The following fields are extra
original = ForeignKeyField(User, related_name='versions')
updated = DateTimeField(default=datetime.utcnow)
revision = IntegerField()
action = TextField() # 'INSERT' or 'UPDATE' (I never delete anything)
So I tried overriding the Model class like this:
class Model(db.Model):
#classmethod
def create_table(cls, fail_silently=False):
db.Model.create_table(cls, fail_silently=fail_silently)
history_table_name = db.Model._meta.db_table + 'history'
# How to create the history table here?
As you can see I manage to create a variable with the history table name, but from there I'm kinda lost.
Does anybody know how I can create a new table which is like the original one, but just with the added 4 fields in there? All tips are welcome!
Maybe something like this:
class HistoryModel(Model):
#classmethod
def create_table(cls...):
# Call parent `create_table()`.
Model.create_table(cls, ...)
history_fields = {
'original': ForeignKeyField(cls, related_name='versions'),
'updated': DateTimeField(default=datetime.utcnow),
'revision': IntegerField(),
'action': TextField()}
model_name = cls.__name__ + 'History'
HistoryModel = type(model_name, (cls,), history_fields)
Model.create_table(HistoryModel, ...)
Also note you'll want to do the same thing for create_indexes().
I'd suggest creating a property or some other way to easily generate the HistoryModel.

NDB: Query for unset properties?

I've had some data being gathered in production for a couple of days with, lets say, the following model:
class Tags(ndb.Model):
dt_added = ndb.DateTimeProperty(auto_now_add=True)
s_name = ndb.StringProperty(required=True, indexed=True)
Imagine I now add a new property to the model:
class Foo(ndb.Model):
is_valid = ndb.BooleanProperty(default=False)
some_key = ndb.KeyProperty(repeated=True)
class Tags(ndb.Model):
dt_added = ndb.DateTimeProperty(auto_now_add=True)
name = ndb.StringProperty(required=True, indexed=True)
new_prop = ndb.StructuredProperty(Foo)
... and gather some more data with this new model.
So now I have a portion of data that has the property new_prop set, and another portion that does not have it set.
My question is: how to I query for the data with the new property new_prop NOT set?
I've tried:
query_tags = Tags.query(Tags.new_prop == None).fetch()
But does not seem to get the data without that property set... Any suggestions?
Thanks!
The Datastore distinguishes between an entity that does not possess a property and one that possesses the property with a null value (None).
It is not possible to query for entities that are specifically lacking a given property. One alternative is to define a fixed (modeled) property with a default value of None, then filter for entities with None as the value of that property.

How to get properties of a model attribute?

Let's say that I have a class such as :
class MyClass(models.Model):
attributeA = models.CharField(max_length=100)
attributeB = models.IntegerField()
attributeC = models.CharField(max_length = 150, blank=True, nullable = True)
attributeD = models.ForeignKey('ModelB',related_name='FK_modelB')
attributeE = models.ManyToManyField('ModelC')
What I want to do is to get the properties of every attribute, not just the name that I got with :
my_instance._meta.get_all_field_name()
(which gave me a list of attributes names). No, what I want is, for every attribute, know what is his type (CharField,IntegerField, ForeignKey, ManyToManyField...), who's related if it's a ForeignKey / ManyToManyField and all the meta data such as max_length and so on.
The aim of it is to serialize a class into a XML and the representation in the XML will be different if it's a ManyToManyField, a ForeignKey or a simple value.
By the way, If anyone know a great class serializer to XML, it would help me a lot !
Thanks for your responses !
Django models _meta.fields is fields list that you can access to get field attributes:
>>> from django.contrib.auth.models import User
>>> u = User.objects.all()[0]
>>> u._meta.fields[1].__class__.__name__
'CharField'
>>> u._meta.fields[1].name
'username'
>>> u._meta.fields[1].max_length
30
>>> u._meta.fields[1].blank
False
# ...
You can get attributes of a specific field by using get_field()
MyClass._meta.get_field('attributeA').max_length

Blank form fields are not accepted

Table in PostgreSQL database:
CREATE TABLE pmss_recordmodel
(
id serial NOT NULL,
"Name" character varying(100),
CONSTRAINT pmss_recordmodel_pkey PRIMARY KEY (id)
)
WITH (OIDS=FALSE);
ALTER TABLE pmss_recordmodel OWNER TO postgres;
Model:
class RecordModel(models.Model):
def __unicode__(self):
return self.Name
Name = models.CharField(max_length = 100, unique = False, null = True, blank = True)
When I POST data with blank Name field, form.is_valid() returns False. Why? Am I missing something?
EDIT:
class RecordForm(forms.ModelForm):
class Meta:
model = RecordModel
Name = forms.CharField(label = "Имя ", widget = forms.TextInput(attrs = {'size': 15}))
Django forms documentation:
By default, each Field class assumes the value is required
Set the "required" argument to False as such:
Name = forms.CharField(required = False, label = "Имя ", widget = forms.TextInput(attrs = {'size': 15}))
You could be having problems with how the field is defined in your database.
This seems like it could be the common situation of doing syncdb with the field initially not having blank=True null=True and then adding those terms later (after initial syncdb) to make the field not required. These settings will not be applied by simply running syncdb again. It requires a database migration or a database flush (will clear existing data, which isn't necessarily bad in development).

Categories