Will Taylor
Will Taylor

Reputation: 2304

GAE Datastore ndb models accessed in 5 different ways

I run an online marketplace. I don't know the best way to access NDB models. I'm afraid it's a real mess and I really don't know which way to turn. If you don't have time for a full response, I'm happy to read an article on NDB best practices

I have these classes, which are interlinked in different ways:

How they're linked.

User - Partner

A 1-to-1 relationship. Both have "email address" fields. If these match, then can retrieve user from partner or vice versa. For example:

 user = self.user
 partner = model.Partner.get_by_email(user.email_address)

Where in the Partner model we have:

 @classmethod
 def get_by_email(cls, partner_email):
    query = cls.query(Partner.email == partner_email)
    return query.fetch(1)[0]

Partner - menuitem

menuitems are children of Partner. Created like so:

myItem = model.menuitem(parent=model.partner_key(partner_name))

menuitems are referenced like this:

menuitems = model.menuitem.get_by_partner_name(partner.name)

where get_by_partner_name is this:

 @classmethod
 def get_by_partner_name(cls, partner_name):
   query = cls.query(
      ancestor=partner_key(partner_name)).order(ndb.GenericProperty("itemid"))
   return query.fetch(300)

and where partner_key() is a function just floating at the top of the model.py file: def partner_key(partner_name=DEFAULT_PARTNER_NAME): return ndb.Key('Partner', partner_name)

Partner - order

Each Partner can have many orders. order has a parent that is Partner. How an order is created:

    partner_name = self.request.get('partner_name')
    partner_k = model.partner_key(partner_name)
    myOrder = model.order(parent=partner_k)

How an order is referenced:

myOrder_k = ndb.Key('Partner', partnername, 'order', ordernumber)
myOrder = myOrder_k.get()

and sometimes like so: order = model.order.get_by_name_id(partner.name, ordernumber)

(where in model.order we have: @classmethod def get_by_name_id(cls, partner_name, id): return ndb.Key('Partner', partner_name, 'order', int(id)).get() )

This doesn't feel particularly efficient, particularly as I often have to look up the partner in the datastore just to pull up an order. For example:

user = self.user
partner = model.Partner.get_by_email(user.email_address)
order = model.order.get_by_name_id(partner.name, ordernumber)

Have tried desperately to get something simple like myOrder = order.get_by_id(ordernumber) to work, but it seems that having a partner parent stops that working.

Preapproval - order.

a 1-to-1 relationship. Each order can have a 'Preapproval'. Linkage: a field in the Preapproval class: order = ndb.KeyProperty(kind=order).

creating a Preapproval:

item = model.Preapproval( order=myOrder.key, ...)

accessing a Preapproval:

preapproval = model.Preapproval.query(model.Preapproval.order == order.key).get()

This seems like the easiest method to me.

TL;DR: I'm linking & accessing models in many ways, and it's not very systematic.

Upvotes: 2

Views: 289

Answers (2)

Hernán Acosta
Hernán Acosta

Reputation: 695

User - Parner

You could replace:

 @classmethod
 def get_by_email(cls, partner_email):
    query = cls.query(Partner.email == partner_email)
    return query.fetch(1)[0]

with:

 @classmethod
 def get_by_email(cls, partner_email):
    query = cls.query(Partner.email == partner_email).get()

But because of transactions issues is better to use entity groups: User should be parent of Partner.

In this case instead of using get_by_email you can get user without queries:

user = partner.key.parent().get()

Or do an ancestor query for getting the partner object:

partner = Partner.query(ancestor=user_key).get()

Query

Don't use fetch() if you don't need it. Use queries as iterators. Instead of:

return query.fetch(300)

just:

return query

And then use query as:

for something in query:
    blah

Relationships: Partner-Menu Item and Partner - Order

Why are you using entity groups? Ancestors are not used for modeling 1 to N relationships (necessarily). Ancestors are used for transactions, defining entity groups. They are useful in composition relationships (e.g.: partner - user)

You can use a KeyProperty for the relationship. (multivalue (i.e. repeated=true) or not, depending on the orientation of the relationship)

Have tried desperately to get something simple like myOrder = order.get_by_id(ordernumber) to work, but it seems that having a partner parent stops that working.

No problem if you stop using ancestors in this relationship.


TL;DR: I'm linking & accessing models in many ways, and it's not very systematic

There is not a systematic way of linking models. It depends of many factors: cardinality, number of possible items in each side, need transactions, composition relationship, indexes, complexity of future queries, denormalization for optimization, etc.

Upvotes: 0

GAEfan
GAEfan

Reputation: 11360

Ok, I think the first step in cleaning this up is as follows:

At the top of your .py file, import all your models, so you don't have to keep using model.ModelName. That cleans up a bit if the code. model.ModelName becomes ModelName.

First best practice in cleaning this up is to always use a capital letter as the first letter to name a class. A model name is a class. Above, you have mixed model names, like Partner, order, menuitem. It makes it hard to follow. Plus, when you use order as a model name, you may end up with conflicts. Above you redefined order as a variable twice. Use Order as the model name, and this_order as the lookup, and order_key as the key, to clear up some conflicts.

Ok, let's start there

Upvotes: 0

Related Questions