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)]
Related
i'm trying to create a e-shop like site, i'm using two models, Product and User. My current problem relays in the fact that when creating new products, those new products needs to be manually inserted in the collection a specific user own, an example:
Current Approach
from api import models as md
john = md.User(name="John", mail="john#test.com").save()
laptop = md.Product()
laptop.name = "Thinkpad T440p"
laptop.price = 190
laptop.description = "foobar"
laptop.owner = john # <-------------- as you can see here i need to specify the owner
laptop.save()
john.products.append(laptop) # <-------------- and then append the new product into the user
Spected behaviour
I want to avoid this approach of doing two operations for something that is essentially the same thing. When creating a new product it should be automatically added to the user's collection. Like this:
...
laptop.owner = john
laptop.save()
john.products.objects()
>>> [<Product Thinkpad T440p>]
Models
Below you will see how i'm creating the models
from api import db
class Product(db.Document):
name = db.StringField()
price = db.DecimalField()
description = db.StringField()
owner = db.ReferenceField('User', required=True)
def __repr__(self):
return '<Product {}>'.format(self.name)
class User(db.Document):
email = db.EmailField()
name = db.StringField()
products = db.ListField(db.ReferenceField('Product'))
def __repr__(self):
return '<User {}>'.format(self.username)
I'm having a dilemma on choosing whether to use ContentType or using ManyToManyField.
Consider the following example:
class Book(Model):
identifiers = ManyToManyField('Identifier')
title = CharField(max_length=10)
class Series(Model):
identifiers = ManyToManyField('Identifier')
book = ForeignKey('Book')
name = CharField(max_length=10)
class Author(Model):
identifiers = ManyToManyField('Identifier')
name = CharField(max_length=10)
class Identifier(Model):
id_type = ForeignKey('IdType')
value = CharField(max_length=10)
class IdType(Model):
# Sample Value:
# Book: ISBN10, ISBN13, LCCN
# Serial: ISSN
# Author: DAI, AIS
name = CharField(max_length=10)
As you notice, Identifier is being used in many places, and in fact, it is so generic that many business related object requires Identifier, similar to how TagItem from the Django examples is being used.
An alternative approach is to generalized this using the Generic Relation.
class Book(Model):
identifiers = GenericRelation(Identifier)
title = CharField(max_length=10)
authors = ManyToManyField(Author)
class Series(Model):
identifiers = GenericRelation(Identifier)
book = ForeignKey('Book')
name = CharField(max_length=10)
class Author(Model):
identifiers = GenericRelation(Identifier)
name = CharField(max_length=10)
class Identifier(Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
id_type = ForeignKey('IdType')
value = CharField(max_length=10)
class IdType(Model):
# Sample Value:
# Book: ISBN10, ISBN13, LCCN, MyLibrary, YourLibrary, XYZLibrary, etc.
# Serial: ISSN, XYZSerial, etc...
# Author: DAI, AIS, XYZAuthor, etc...
name = CharField(max_length=10)
I'm unsure if I'm raising the right concern regarding Generic Relation.
I'm worried about the data growth of the Identifier table, as it will grow very fast on one table, for example:
100,000 books, average 4 identifiers each. (total 400,000 identifier records)
average 2 authors per book (total 200,000 identifier records)
For each record in book, 4-6x data increased in identifier table. Soon, Identifier Table will be millions of records. Will the queries becoming very slow in the long run? Moreover, I believed that identifier is a field that being queried and used in the application.
Is this generalization correctly done? As in, Author Identifier is completely unrelated with Book Identifier and should have its own BookIdentifier and AuthorIdentifier on its own. Although they seems to have IdType.name and IdType.value pattern, but the domain are completely not related, one is author, the other is book. Should they be generalized? Why not?
What problem could there be if I were to implement under GenericRelation model?
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).
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)
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.