GAE inheritance in datastore - python

I am trying to create something like a folder structure for saving to GAE ndb datastore.
I will be saving a top root folder (MjlistitemFolder) to the datastore.
A MjlistitemFolder can have a number of other subitems (Mjlistitem) in its items property.
Ultimately the content of a folder items will be one of these: MjlistitemFolder, MjlistitemJobGeneric, MjlistitemJobApp
This all works if I create this structure in memory.
But after put()ing it away and reloading the root folder from the datastore, I don't get the same structure back.
class Mjlistitem(ndb.Model):
pass
class MjlistitemFolder(Mjlistitem):
title = ndb.StringProperty()
items = ndb.StructuredProperty(Mjlistitem, repeated=True)
class MjlistitemJob(Mjlistitem):
pass
class MjlistitemJobGeneric(MjlistitemJob):
jobtype = ndb.IntegerProperty()
class MjlistitemJobApp(MjlistitemJob):
appleid = ndb.StringProperty()
I get these warnings:
WARNING 2014-04-04 07:14:17,506 model.py:2359] Skipping unknown structured subproperty (items.items.appleid) in repeated structured property (items of MjlistitemFolder)
WARNING 2014-04-04 07:14:17,506 model.py:2359] Skipping unknown structured subproperty (items.items.jobtype) in repeated structured property (items of MjlistitemFolder)
WARNING 2014-04-04 07:14:17,506 model.py:2359] Skipping unknown structured subproperty (items.items.appleid) in repeated structured property (items of MjlistitemFolder)
It seems like the db→instance process renders the stuff in items to be of Mjlistitem class only. How do I make them appear as their real inherited classes?
This is how I create a test structure:
rootfolder = MjlistitemFolder(title="root")
subfolder = MjlistitemFolder(title="Cool things")
subfolder.items.append(MjlistitemJobApp(appleid="281796108")) # evernote
subfolder.items.append(MjlistitemJobGeneric(jobtype=3)) # phone number
subfolder.items.append(MjlistitemJobApp(appleid="327630330")) # dropbox
rootfolder.items.append(MjlistitemJobGeneric(jobtype=15)) # passbook
rootfolder.items.append(subfolder)
rootfolder.items.append(MjlistitemJobGeneric(jobtype=17)) # appstore
rootfolder.put()

use Polymodel with repeated KeyProperty
The StructuredProperty need to be changed to KeyProperty because:
TypeError: This StructuredProperty cannot use repeated=True because its model class (Mjlistitem) contains repeated properties (directly or indirectly).
The Model
from google.appengine.ext import ndb
from google.appengine.ext.ndb import polymodel
class Mjlistitem(polymodel.PolyModel):
pass
class MjlistitemFolder(Mjlistitem):
title = ndb.StringProperty()
# StructuredProperty won't allow you to apply on nested model, use key property instead
items = ndb.KeyProperty(kind=Mjlistitem, repeated=True)
class MjlistitemJob(Mjlistitem):
pass
class MjlistitemJobGeneric(MjlistitemJob):
jobtype = ndb.IntegerProperty()
class MjlistitemJobApp(MjlistitemJob):
appleid = ndb.StringProperty()
The Usage
def test():
rootfolder = MjlistitemFolder(title="root")
subfolder = MjlistitemFolder(title="Cool things")
subfolder.items.append(MjlistitemJobApp(appleid="281796108").put()) # evernote
subfolder.items.append(MjlistitemJobGeneric(jobtype=3).put()) # phone number
subfolder.items.append(MjlistitemJobApp(appleid="327630330").put()) # dropbox
rootfolder.items.append(MjlistitemJobGeneric(jobtype=15).put()) # passbook
rootfolder.items.append(subfolder.put())
rootfolder.items.append(MjlistitemJobGeneric(jobtype=17).put()) # appstore
rootfolder.put()
another thing should watch out.
the repeated property won't perform well if there are too many items in one folder, so it would be better if
class Mjlistitem(polymodel.PolyModel):
parent = ndb.KeyProperty(kind="Mjlistitem")
def set_parent(self, parent):
assert isinstance(parent, MjlistitemFolder)
self.parent = parent.key
class MjlistitemFolder(Mjlistitem):
title = ndb.StringProperty()
class MjlistitemJob(Mjlistitem):
pass
class MjlistitemJobGeneric(MjlistitemJob):
jobtype = ndb.IntegerProperty()
class MjlistitemJobApp(MjlistitemJob):
appleid = ndb.StringProperty()
The Usage
def test():
rootfolder = MjlistitemFolder(title="root")
rootfolder.put()
subfolder = MjlistitemFolder(title="Cool things")
subfolder.set_parent(rootfolder)
subfolder.put()
a = MjlistitemJobApp(appleid="281796108")
a.set_parent(subfolder)
a.put()
b = MjlistitemJobGeneric(jobtype=3)
b.set_parent(subfolder)
b.put()
c = MjlistitemJobApp(appleid="327630330")
c.set_parent(subfolder)
c.put()

Related

Django Integrated Test Passing When it Should Fail

I'm in the process of creating an assessment system using Django; however, I have an integrated test that passes and I'm not sure as to why (it should be failing). In the test, I set the grade field of the bobenrollment object to "Excellent". As you can see from the models below, the Enrollment model doesn't have a grade field (none of the models do). I was under the impression that dot notation of model objects would access the model fields (I'm probably incorrect about this). I don't want to write ineffective tests, so I would like to know what makes this test pass and what I should do to make it break. Thanks!
class ClassAndSemesterModelTest(TestCase):
def add_two_classes_to_semester_add_two_students_to_class(self):
first_semester = Semester.objects.create(text='201530')
edClass = EdClasses.objects.create(name='EG 5000')
edClass2 = EdClasses.objects.create(name='EG 6000')
first_semester.classes.add(edClass)
first_semester.classes.add(edClass2)
bob = Student.objects.create(name="Bob DaBuilder")
jane = Student.objects.create(name="Jane Doe")
bobenrollment = Enrollment.objects.create(student=bob, edclass=edClass)
janeenrollment = Enrollment.objects.create(student=jane,edclass=edClass)
bobenrollment2 = Enrollment.objects.create(student=bob,edclass=edClass2)
janeenrollment2 = Enrollment.objects.create(student=jane,edclass=edClass2)
def test_students_link_to_enrollments(self):
self.add_two_classes_to_semester_add_two_students_to_class()
edclass1 = EdClasses.objects.get(name="EG 5000")
bob = Student.objects.get(name="Bob DaBuilder")
#The three lines below are the subject of my question
bobenrollment = Enrollment.objects.get(edclass=edclass1, student=bob)
bobenrollment.grade = "Excellent"
self.assertEqual(bobenrollment.grade, "Excellent")
And the models below:
from django.db import models
class Student(models.Model):
name = models.TextField(default="")
def __str__(self):
return self.name
#TODO add models
class EdClasses(models.Model):
name = models.TextField(default='')
students = models.ManyToManyField(Student, through="Enrollment")
def __str__(self):
return self.name
class Semester(models.Model):
text = models.TextField(default='201530')
classes = models.ManyToManyField(EdClasses)
def __str__(self):
return self.text
class Enrollment(models.Model):
student = models.ForeignKey(Student)
edclass = models.ForeignKey(EdClasses)
Requirements.txt
beautifulsoup4==4.4.1
Django==1.5.4
ipython==3.1.0
LiveWires==2.0
nose==1.3.3
Pillow==2.7.0
projectname==0.1
pyperclip==1.5.11
pytz==2015.2
requests==2.10.0
selenium==2.53.6
six==1.9.0
South==1.0.2
swampy==2.1.7
virtualenv==1.11.5
I was under the impression that dot notation of model objects would access the model fields (I'm probably incorrect about this)
You're correct about this. What you're not taking into account is the fact that you can dynamically add properties to python objects. For instance:
In [1]: class MyClass():
...: pass
...:
In [2]: a = MyClass()
In [3]: a.im_a_property = 'hello'
In [4]: print a.im_a_property
hello
As you can see, the a instance will have the im_a_propery property even though it's not defined by the class. The same applies for the following line in your code:
bobenrollment.grade = "Excellent"
Django models override this behavior so you can seamlessly get DB values as properties of your model instance, but the instance is just a regular python object.
If you want to test the grade property gets saved correctly, you should modify your test to add the value of grade when creating the record and making sure the instance you assert against is the one you read from your DB (i.e. not modifying it beforehand).

How can I use multiple models to point to the same collection?

I have a single collection that can represent multiple types of data:
class Taxes(db.Document):
meta = {'collection': 'taxes'}
type = db.StringField() # State, local, federal
owner = db.ReferenceField(User, unique=True)
name = db.StringField()
fiscal_year = db.IntField()
What I am wanting to do is have either a DynamicEmbeddedDocument or make this a DynamicDocument to hold different models.
For example:
class Taxes(db.Document):
...
# This is made up syntax
data = db.EmbeddedDocumentField(StateTaxes, LocalTaxes, FederalTaxes)
Or:
class Taxes(db.DynamicDocument):
...
class StateTaxes(Taxes):
state_name = db.StringField()
class LocalTaxes(Taxes):
locality_name = db.StringField()
The goal is to do this:
# Embedded Dynamic Document example
taxes = Taxes.objects(owner=current_user).all()
state_taxes = [tax.data for tax in taxes if tax.type == 'state']
state_names = [tax_data.state_name for tax_data in state_taxes]
# Dynamic Document example
taxes = Taxes.objects(owner=current_user).all()
state_taxes = [tax for tax in taxes if tax.type == 'state']
state_names = [tax.state_name for tax in state_taxes]
Notes:
I must be able to perform 1 query to get back all types**.
Models should be separate in order to allow for clean definitions.
This example is very small, there would be a growing number of Models with very different definitions**.
All Models will have 4 or 5 fields that are the same.
The dynamic data should be relatively easy to query.
**These are the main reasons I am not using separate collections
Is this possible?
You could make a base class that covers all the base attributes (fields) and methods that you need. For example:
class BaseTaxes(db.Document):
name = db.StringField()
value = db.IntegerField()
meta = {'allow_inheritance': True}
def apply_tax(self, value):
return value*(1+self.value)
With this base class you can then create different versions:
class StateTaxes(BaseTaxes):
state = db.StringField()
As such the StateTaxes class inherits both attributes of BaseTaxes and its methods (more details here). Because it inherits the BaseTaxes class, it will be saved in the same collection (BaseTaxes) and queries can reach all subclasses:
results = BaseTaxes.objects().all()
And then, to split results by subclass:
state_taxes = [item for item in results if isinstance(item,StateTaxes)]

Determine if a property is a backref in sqlalchemy

I have the following relationship set up in a model:
role_profiles = Table('roleprofile', Base.metadata,
Column('role_id', Integer, ForeignKey('role.id')),
Column('profile_id', Integer, ForeignKey('profile.id'))
)
class profile(Base):
__tablename__ = 'profile'
# Columns...
roles = relationship('role', secondary=role_profiles, backref='profiles')
class role(Base):
__tablename__ = 'role'
# Columns...
So as I now understand that it works is that the roles property on the profile object will contain a list of role classes (which it does).
What I want to do is to serialize for each property of the model class generically. It works fine for the top class profile and I determine that there is a list of roles that I should recurse into:
# I need a statement here to check if the field.value is a backref
#if field.value is backref:
# continue
if isinstance(field.value, list):
# Get the json for the list
value = serialize.serialize_to_json(field.value)
else:
# Get the json for the value
value = cls._serialize(field.value)
The problem is that the backref of the relationship adds a pointer back to the profile. The same profile is then serialized and it recurse the roles over and over again until stack overflow.
Is there a way to determine that the property is a backref added by the relationship?
Update
Maybe I should add that it works fine in this case if I remove the backref since I don't need it but I would like to keep it in.
Update
As a temporary fix I added a class property to my base class:
class BaseModelMixin(object):
"""Base mixin for models using stamped data"""
__backref__ = None
and add it like this:
class role(Base):
__tablename__ = 'role'
__backref__ = ('profiles', )
# Columns...
and use it like this in my recursion:
if self.__backref__ and property_name in self.__backref__:
continue
If there is a better way please let me know because this doesn't look optimal.
Not sure if this is the best practice, but this code works for me. It returns True if the attribute is a reference, False if a regular column type.
def is_relation(orm_object, attr_name):
return hasattr(getattr(orm_object.__class__, attr_name).property, 'mapper')
You can create a __relationships__ in your class BaseModelMixin as a #property, which has a list of all relationships name which are not as a backref name in a model.
class BaseModelMixin(object):
"""Base mixin for models using stamped data"""
#property
def __relationships__(self):
"""
Return a list of relationships name which are not as a backref
name in model
"""
back_ref_relationships = list()
items = self.__mapper__.relationships.items()
for (key, value) in items:
if isinstance(value.backref, tuple):
back_ref_relationships.append(key)
return back_ref_relationships
As you have two class profile and role, so
>>> p = profile()
>>> p.__relationships__
# ['roles']
>>> r = role()
>>> r.__relationships__
# []
have a look at inspect
e.g.
from sqlalchemy import inspect
mapper = inspect(MyModelClass)
# dir(mapper)
# mapper.relationships.keys()

Different default values for Model siblings Google App Engine

I am using an inherited modelling schema for my site, it has every media element under one common PolyModel base with every different element by themselves like so:
class STSeasonMedia(polymodel.PolyModel):
season = db.ReferenceProperty(STSeason,collection_name='related_media')
description = db.StringProperty()
visible = db.BooleanProperty(default=True)
priority = db.IntegerProperty(default=10)
So I want the "Inheriting" Models to have some other fields but also different default values, for example:
class STVideo(STSeasonMedia):
video_id = db.StringProperty()
provider = db.StringProperty()
priority = db.IntegerProperty(default = 100)
class STThumb(STSeasonMedia):
picture = db.ReferenceProperty(STPicture,collection_name='thumbs')
url = db.StringProperty()
size = db.StringProperty()
class STNote(STSeasonMedia):
content = db.TextProperty()
visible = db.BooleanProperty(default=False)
priority = db.IntegerProperty(default = 1)
Is there a way to set this different default values, they may change afterwards but in the beginning must by those values. Any idea?
I think your best solution may be to provide an __init__ method to your derived models. It can provide a modified default value for certain properties if none was provided by the user.
For example, your STVideo class, which wants a different default priority should be able to use this:
def __init__(self, priority=100, **kwargs):
super(STVideo, self).__init__(priority=priority, **kwargs)

How does django one-to-one relationships map the name to the child object?

Apart from one example in the docs, I can't find any documentation on how exactly django chooses the name with which one can access the child object from the parent object. In their example, they do the following:
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
def __unicode__(self):
return u"%s the restaurant" % self.place.name
# Create a couple of Places.
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()
# Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
# A Restaurant can access its place.
>>> r.place
<Place: Demon Dogs the place>
# A Place can access its restaurant, if available.
>>> p1.restaurant
So in their example, they simply call p1.restaurant without explicitly defining that name. Django assumes the name starts with lowercase. What happens if the object name has more than one word, like FancyRestaurant?
Side note: I'm trying to extend the User object in this way. Might that be the problem?
If you define a custom related_name then it will use that, otherwise it will lowercase the entire model name (in your example .fancyrestaurant). See the else block in django.db.models.related code:
def get_accessor_name(self):
# This method encapsulates the logic that decides what name to give an
# accessor descriptor that retrieves related many-to-one or
# many-to-many objects. It uses the lower-cased object_name + "_set",
# but this can be overridden with the "related_name" option.
if self.field.rel.multiple:
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
return None
return self.field.rel.related_name or (self.opts.object_name.lower() + '_set')
else:
return self.field.rel.related_name or (self.opts.object_name.lower())
And here's how the OneToOneField calls it:
class OneToOneField(ForeignKey):
... snip ...
def contribute_to_related_class(self, cls, related):
setattr(cls, related.get_accessor_name(),
SingleRelatedObjectDescriptor(related))
The opts.object_name (referenced in the django.db.models.related.get_accessor_name) defaults to cls.__name__.
As for
Side note: I'm trying to extend the
User object in this way. Might that be
the problem?
No it won't, the User model is just a regular django model. Just watch out for related_name collisions.

Categories

Resources