ndb design many-to-many with parent child change - python

after a lot of research here and lots of reading here for me is still not clear in some cases how parent and child works:
+------------------------+ +------------------------+
| Room Model | +----------------+ | Room Model |
| <-----------+ +-------> |
+--+----------------+----+ | Supporter | +------------------------+
| | +----------------+ | |
| +------v-----+ | +------v-----+
| | | | | |
| | customer | | | customer |
| | | | | |
| +--+---------+ | +--+---------+
| | | |
| | | |
+--v+-----------v-+ +--v+-----------v-+
| | | |
| Device | | Device |
| | | |
+---+-----------+-+ +---+-----------+-+
| | | |
| | | |
| +-----v------------+ | +-----v------------+
| | | | | |
| | Issues / tickets | | | Issues / tickets |
| +------------------+ | +------------------+
| |
+v---------------+ +--------+ +v---------------+ +--------+
| pdf / pics | |Blob_key| | pdf / pics | |Blob_key|
| | | | | | | |
+----------------+-->--------+ +----------------+-->--------+
Here is my situation:
I have different rooms and the room ,model are the highest entity in the relationship, it does not change, but all the childs connected to it changes and some of them have more childs. So a room can have several customers at the same time and several devices which 1- some belong to the room and 2- some were allocated to the room:
class Room(MainModel):
# room_number is ID or key of room which is the parent of all entities
room_number = ndb.StringProperty()
# I don't know if this is the correct way to do this.
devices = ndb.KeyProperty(kind='Device', repeated=True)
# maybe this should be in the Device model?
devices_allocated = ndb.KeyProperty(kind='Device.allocation', repeated=True)
based_supporters = ndb.KeyProperty(kind='Supporter')
supporter = ndb.KeyProperty(kind='Supporter')
customers = ndb.KeyProperty(kind='Customers', repeated="True")
# Or should I go for ndb.KeyProperty(kind='Issue', repeated="True")?
issues = ndb.KeyProperty(kind='Vehicle.issues', repeated="True")
Then I have supporters which takes care of the rooms contents(customers and devices) they can support more than 1 room and and consequently several customers and devices(childs of the rooms) and have have one base room, means they are sitting in a room monitoring other rooms...
class Supporter(MainModel):
# id is ID or key of supporter which is child of room but becomes child of another room if changed
id = ndb.StringProperty()
name = ndb.StringProperty()
#I wonder if is possible to get these entities from the room model... Like this
devices = ndb.KeyProperty(kind='Room.devices', repeated=True)
customers = ndb.KeyProperty(kind='Customer', repeated=True)
#room which supporter stays
base_room = ndb.KeyProperty(kind='Room')
#rooms which supporter supports
rooms = ndb.KeyProperty(kind='Room', repeated="True")
Then I have customers, which are in a room(parent) and can use a device(child of the room) which should be able to be replaced depending on the situation, customers can change rooms and therefore the device of the room:
class Customer(MainModel):
# id is ID or key of customer which is child of room but becomes child of another room if changed
id = ndb.StringProperty()
name = ndb.StringProperty()
# device customer is using which can change
device = ndb.KeyProperty(kind='Device')
supporter = ndb.KeyProperty(kind='Supporter')
#customer room which can change
room = ndb.KeyProperty(kind='Room')
Then I have the devices which are always in a room(parent) or going to another room(parent change) the devices have files, pics(children of device) which moves with the device and it gets to another room, exception of the issue(child of the device and indirect child of the room) which belongs to the device but should show the room number it was created.
class Device(MainModel):
# id is ID or key of device which is child of room but becomes child of another room if changed
id = ndb.StringProperty()
name = ndb.StringProperty()
customer = ndb.KeyProperty(kind='Customer')
supporter = ndb.KeyProperty(kind='Supporter')
# device base room
base_room = ndb.KeyProperty(kind='Room')
# device actual room which can change
room = ndb.KeyProperty(kind='Room')
issue = ndb.KeyProperty(kind="issue", repeated="True")
file = ndb.KeyProperty(kind="File", repeated="True")
pics = ndb.KeyProperty(kind="Pics", repeated="True")
Finally I have issues and tickets from the devices(parent of the tickets and issues)
class Issue(MainModel):
# id is ID or key of issue which is child of device
id = ndb.StringProperty()
title = ndb.StringProperty()
# This is the parent of the issue model
device = ndb.KeyProperty(kind='device')
# place the issue were created
room = ndb.KeyProperty(kind='Room')
# issue child's attachments not the same as device attachments
file = ndb.KeyProperty(kind="File", repeated="True")
pics = ndb.KeyProperty(kind="Pics", repeated="True")
Is this the correct way to apply the key property? looks very confusion to me... specially in this complex relationship design model
what are you thinking? I there another way of doing this? How would be the best or more efficient approach to change parent or child or both dynamically?
I will try it an I will keep updating here, please feel free to share opinion why and how could be better or not.
Regards

Related

Python, Django: Query on combined models?

inside my app I have multiple models, like:
models.py:
class Company(models.Model):
name = models.CharField(max_length=100)
class Coworker(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
company = models.ForeignKey(Company, null=False, blank=False, on_delete=models.CASCADE)
As you can see, a Company can contain one, multiple or no Coworker! Is it possible to query Company but also receive the data from connected Coworker? For example something like this:
id | Company | Coworker
1 | Company_A | Coworker_A
2 | Company_B |
3 | Company_C | Coworker_B
4 | Company_C | Coworker_C
5 | Company_C | Coworker_D
6 | Company_D | Coworker_E
7 | Company_D | Coworker_F
8 | Company_E |
9 | Company_F | Coworker_G
10 | Company_F | Coworker_H
...
My problem is, that I can't query on the Coworker, because I don't want to miss those Companies that have no related data!
Thanks for your help and have a great day!
Quite simply, query by company and prefetch results for workers :
Company.objects.prefetch_related("coworker_set").all()
What this will do is give you a list of companies containing their list of workers in the attribute coworker_set. It will also populate those attributes in a single query (that's the whole point of prefetch_related).
More specifically, here is how you could use such a query :
for company in Company.objects.prefetch_related("coworker_set").all():
print(company.name)
for coworker in company.coworker_set.all():
print(coworker.first_name)
print(coworker.last_name)
This guarantees that you will iterate through all companies as well as through all coworkers, and you will only iterate through each one once (indeed, if you queried by coworkers you would see some companies multiple times and others none at all).

Django many to many avoiding duplication

I'm getting duplication in my path (many to many) table, and would like it only to contain unique items.
models.py
class Image(models.Model):
path = models.CharField(max_length=128)
class User(models.Model):
username = models.CharField(max_length=32)
created = models.DateTimeField()
images = models.ManyToManyField(Image, through='ImageUser')
class ImageUser(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ForeignKey(Image, on_delete=models.CASCADE)
But, I seem to be able to create more than one image with the same path. I would like one unique image path to point to multiple users without having duplicate images in the Image table.
u = User.objects.create(username='AndyApple')
i = Image(path='img/andyapple.jpg')
i.save()
ui = ImageUser(user=u, image=i)
ui.save()
u2 = User.objects.create(username='BettyBanana')
ui = ImageUser(user=u2, image=i)
This seems to create two rows in the images table for the same image. The docs suggest this should not happen ManyToManyField.through
Thanks!
Are you sure your code adds duplicates to Image and not to ImageUser (which is how many-to-many table works)?
-------------------- --------------------------- ----------------------------
| Users | | ImageUser | | Image |
-------------------- --------------------------- ----------------------------
| id | username | | id | user_id | image_id | | id | path |
-------------------- --< --------------------------- >-- ----------------------------
| 1 | AndyApple | | 1 | 1 | 1 | | 1 | 'img/andyapple.jpg' |
-------------------- --------------------------- ----------------------------
| 2 | BettyBanana | | 2 | 2 | 1 |
-------------------- ---------------------------
But anyway, the problem is not right here, if you want:
"one unique image path to point to multiple users without having
duplicate images in the Image table."
then you have to define the field as unique, see code sample below. In this case if you will try to save the image with the pathy that is already in DB the exception will be raised. But note that in this case if two users upload different images, but with the same name, then the last uploaded image will be used for both users.
class Image(models.Model):
path = models.CharField(max_length=128, unique=True)

Using terminaltables, how can I get all my data in a single table, rather than split across multiple tables?

I’m having a problem printing a table with terminaltables.
Here's my main script:
from ConfigParser import SafeConfigParser
from terminaltables import AsciiTable
parser = SafeConfigParser()
parser.read('my.conf')
for section_name in parser.sections():
description = parser.get(section_name,'description')
url = parser.get(section_name,'url')
table_data = [['Repository', 'Url', 'Description'], [section_name, url, description]]
table = AsciiTable(table_data)
print table.table
and here's the configuration file my.conf:
[bug_tracker]
description = some text here
url = http://localhost.tld:8080/bugs/
username = dhellmann
password = SECRET
[wiki]
description = foo bar bla
url = http://localhost.tld:8080/wiki/
username = dhellmann
password = SECRET
This print me a table for each entry like this:
+-------------+---------------------------------+------------------------+
| Repository | Url | Description |
+-------------+---------------------------------+------------------------+
| bug_tracker | http://localhost.foo:8080/bugs/ | some text here |
+-------------+---------------------------------+------------------------+
+------------+---------------------------------+-------------+
| Repository | Url | Description |
+------------+---------------------------------+-------------+
| wiki | http://localhost.foo:8080/wiki/ | foo bar bla |
+------------+---------------------------------+-------------+
but what I want is this:
+-------------+---------------------------------+------------------------+
| Repository | Url | Description |
+-------------+---------------------------------+------------------------+
| bug_tracker | http://localhost.foo:8080/bugs/ | some text here |
+-------------+---------------------------------+------------------------+
| wiki | http://localhost.foo:8080/wiki/ | foo bar bla |
+-------------+---------------------------------+------------------------+
How can I modify the script to get this output?
The problem is that you recreate table_data and table on each iteration of the loop. You print on each iteration, and then the old data gets thrown away and a new table gets started from scratch. There’s no overlap in the body of the tables you’re creating.
You should have a single table_data, which starts with the headings, then you gather all the table data before printing anything. Add the new entries on each iteration of the loop, and put the print statement after the for loop is finished. Here’s an example:
from ConfigParser import SafeConfigParser
from terminaltables import AsciiTable
parser = SafeConfigParser()
parser.read('my.conf')
table_data = [['Repository', 'Url', 'Description']]
for section_name in parser.sections():
description = parser.get(section_name,'description')
url = parser.get(section_name,'url')
table_data.append([section_name, url, description])
table = AsciiTable(table_data)
print table.table
and here’s what it outputs:
+-------------+---------------------------------+----------------+
| Repository | Url | Description |
+-------------+---------------------------------+----------------+
| bug_tracker | http://localhost.tld:8080/bugs/ | some text here |
| wiki | http://localhost.tld:8080/wiki/ | foo bar bla |
+-------------+---------------------------------+----------------+
If you want to have a horizontal rule between the bug_tracker and wiki lines, then you need to set table.inner_row_border to True. So you’d replace the last two lines with:
table = AsciiTable(table_data)
table.inner_row_border = True
print table.table

How to use Pretty table with flask

I am programming my first website with flask one of my sections is a list of all sub-users for the teacher's class so how can I use prettytable with flask to get a table my end goal is a table that looks like this (just the data)
Teacher | Student | Id | GPA | Last sign learned
----------------------------------------------------------------
Medina | John doe | 19500688 | 4.0 | Bad
Medina | Samantha babcock | 91234094 | 2.5 | Toilet
Jonson | Steven smith | 64721881 | 3.0 | Santa
How can I do this preferably with Pretty table but any method would be great!
Hello so your form is simple to create but so lets begin with static information with python
def function():
from prettytable import *
table = PrettyTable(["Teacher","Student"," ID","GPA"," Last sign learned "])
table.add_row(["Medina","John doe","19500688","4.0","Bad"])
table.add_row(["Medina","Samantha babcock ","91234094","2.5","Toilet"])
table.add_row(["Jonson","Steven smith","64721881","3.0","Santa"])
return render_template("info.html", tbl=table.get_html_string(attributes = {"class": "foo"}))
Now for you HTML:
{%extends "template.html"%}
{{tbl|safe}}

Can I append twice the same object to an InstrumentedList in SQLAlchemy?

I have a pretty simple N:M relationship in SqlAlchemy 0.6.6. I have a class "attractLoop" that can contain a bunch of Media (Images or Videos). I need to have a list in which the same Media (let's say image) can be appended twice. The relationship is as follows:
The media is a base class with most of the attributes Images and Videos will share.
class BaseMedia(BaseClass.BaseClass, declarativeBase):
__tablename__ = "base_media"
_polymorphicIdentity = Column("polymorphic_identity", String(20), key="polymorphicIdentity")
__mapper_args__ = {
'polymorphic_on': _polymorphicIdentity,
'polymorphic_identity': None
}
_name = Column("name", String(50))
_type = Column("type", String(50))
_size = Column("size", Integer)
_lastModified = Column("last_modified", DateTime, key="lastModified")
_url = Column("url", String(512))
_thumbnailFile = Column("thumbnail_file", String(512), key="thumbnailFile")
_md5Hash = Column("md5_hash", LargeBinary(32), key="md5Hash")
Then the class who is going to use these "media" things:
class TestSqlAlchemyList(BaseClass.BaseClass, declarativeBase):
__tablename__ = "tests"
_mediaItems = relationship("BaseMedia",
secondary=intermediate_test_to_media,
primaryjoin="tests.c.id == intermediate_test_to_media.c.testId",
secondaryjoin="base_media.c.id == intermediate_test_to_media.c.baseMediaId",
collection_class=list,
uselist=True
)
def __init__(self):
super(TestSqlAlchemyList, self).__init__()
self.mediaItems = list()
def getMediaItems(self):
return self._mediaItems
def setMediaItems(self, mediaItems):
if mediaItems:
self._mediaItems = mediaItems
else:
self._mediaItems = list()
def addMediaItem(self, mediaItem):
self.mediaItems.append(mediaItem)
#log.debug("::addMediaItem > Added media item %s to %s. Now length is %d (contains: %s)" % (mediaItem.id, self.id, len(self.mediaItems), list(item.id for item in self.mediaItems)))
def addMediaItemById(self, mediaItemId):
mediaItem = backlib.media.BaseMediaManager.BaseMediaManager.getById(int(mediaItemId))
if mediaItem:
if mediaItem.validityCheck():
self.addMediaItem(mediaItem)
else:
raise TypeError("Media item with id %s didn't pass the validity check" % mediaItemId)
else:
raise KeyError("Media Item with id %s not found" % mediaItem)
mediaItems = synonym('_mediaItems', descriptor=property(getMediaItems, setMediaItems))
And the intermediate class to link both of the tables:
intermediate_test_to_media = Table(
"intermediate_test_to_media",
Database.Base.metadata,
Column("id", Integer, primary_key=True),
Column("test_id", Integer, ForeignKey("tests.id"), key="testId"),
Column("base_media_id", Integer, ForeignKey("base_media.id"), key="baseMediaId")
)
When I append the same Media object (instance) twice to one instances of that TestSqlAlchemyList, it appends two correctly, but when I retrieve the TestSqlAlchemyList instance from the database, I only get one. It seems to be behaving more like a set.
The intermediate table has properly all the information, so the insertion seems to be working fine. Is when I try to load the list from the database when I don't get all the items I had inserted.
mysql> SELECT * FROM intermediate_test_to_media;
+----+---------+---------------+
| id | test_id | base_media_id |
+----+---------+---------------+
| 1 | 1 | 1 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 4 | 1 | 2 |
| 5 | 1 | 1 |
| 6 | 1 | 1 |
| 7 | 2 | 1 |
| 8 | 2 | 1 |
| 9 | 2 | 1 |
| 10 | 2 | 2 |
| 11 | 2 | 1 |
| 12 | 2 | 1 |
As you can see, the "test" instance with id=1 should have the media [1, 1, 2, 2, 1, 1]. Well, it doesn't. When I load it from the DB, it only has the media [1, 2]
I have tried to set any parameter in the relationship that could possibly smell to list... uselist, collection_class = list... Nothing...
You will see that the classes inherit from a BaseClass. That's just a class that isn't actually mapped to any table but contains a numeric field ("id") that will be the primary key for every class and a bunch of other methods useful for the rest of the classes in my system (toJSON, toXML...). Just in case, I'm attaching an excerpt of it:
class BaseClass(object):
_id = Column("id", Integer, primary_key=True, key="id")
def __hash__(self):
return int(self.id)
def setId(self, id):
try:
self._id = int(id)
except TypeError:
self._id = None
def getId(self):
return self._id
#declared_attr
def id(cls):
return synonym('_id', descriptor=property(cls.getId, cls.setId))
If anyone can give me a push, I'll appreciate it a lot. Thank you. And sorry for the huge post... I don't really know how to explain better.
I think what you want is a described in the documentation as "Extra Fields in Many-to-Many Relationships". Rather than storing a unique row in the database foreach "link", between attractLoop and Media, you would store a single association and specify (as a part of the link object model) how many times it is referenced and/or in which location(s) in the final list the media should appear. This is a different paradigm from where you started, so it'll certainly require some re-coding, but I think it addresses your issue. You would likely need to use a property to redefine how to add or remove Media from the attactLoop.

Categories

Resources