PonyORM orphaned items when clearing a set that defines one-to-many relation - ponyorm

I have a basic relation defined as follows:
db = Database('sqlite', 'test_db.sqlite', create_db=True)
class WOEID(db.Entity):
woeid = PrimaryKey(int)
iso = Optional(str)
name = Required(str)
language = Optional(str)
place_type = Required(str)
parent_id = Required(int)
trends = Set('Trend')
ancestry = Optional(str)
class Trend(db.Entity):
woeid = Required(int)
events = Optional(str)
name = Required(str)
promoted_content = Optional(str)
query = Required(str)
url = Required(str)
location = Optional(WOEID)
db.generate_mapping(create_tables=True)
Now, I add some items to WOEID.trends within a function decorated with #db_session. This works as expected.
Now I try to update WOEID.trends by first reading an object using
location = WOEID.get(woeid = some_woeid)
later on I issue
location.trends.clear()
to delete old entries and I add new items to the trends set.
In the generated Trends table after this operation I have the items added, but previous items (cleared from the set) are not deleted, they stay in the database with 'location' field nulled (they are dereferenced I guess).
How should I perform the operation outlined above to get read of orphaned items?

There are two kinds of one-to-many relationships in PonyORM. The first kind of relationship is when one end of relationship is Set and the other end of relationship is Required. In that case when you remove an item from the collection this item will be deleted. For example we can define two entities Article and Comment in the following way:
class Article(db.Entity):
author = Required(User)
text = Required(str)
comments = Set('Comment')
class Comment(db.Entity):
author = Required(User)
text = Required(str)
article = Required(Article)
In that case, when you perform article.comments.clear() all comment will be deleted, because the Comment.article attribute is required and a comment cannot exist without an article.
The other kind of relationship is where Comment.article attribute is defined as Optional:
class Comment(db.Entity):
author = Required(User)
text = Required(str)
article = Optional(Article)
In that case a comment can exist without any article, and when you remove the comment from the Article.comments collection it remains in the database, but the Comment.article attribute value is set to NULL.
You can find orphaned items by executing the following query:
select(c for c in Comment if c.article is None)
or, equivalently
Comment.select(lambda c: c.article is None)
In some cases it may be desirable to define attribute as Optional, but perform cascade delete on removing item from the collection. In order to do this, you can specify cascade_delete option for the Set attribute:
class Article(db.Entity):
author = Required(User)
text = Required(str)
comments = Set('Comment', cascade_delete=True)
class Comment(db.Entity):
author = Required(User)
text = Required(str)
article = Optional(Article)
Then if you do article.comments.clear() then all removed comments will be deleted from the database.

Related

How to query over a list of embedded documents with allow_inheritance

If I have the following schema:
class Post(EmbeddedDocument):
title = StringField(max_length=120, required=True)
meta = {'allow_inheritance': True}
class TextPost(Post):
content = StringField()
class MoviePost(Post):
author = ReferenceField(Authors)
class Record(Document):
posts = ListField(EmbeddedDocumentField(Post))
And I do the following query:
author = Author.objects.get_or_404(id = id)
records = Record.objects(posts__author = author)
records.count()
I get the following error:
AttributeError: 'author' object has no attribute 'get'
This seems to only happen with allow_inheritance when certain objects may or may not have the 'author' field. If the field exists on all objects, such as the 'title' field, the query works fine.
It seems that this is still an open issue in mongoengine that has yet to be addressed. One way around it is to use match. For example, the following does the trick:
records = Record.objects(posts__match = { 'author': author })

MotorEngine - How to represent the equivalent of the foreign key in model?

In MongoDB I have a document that represents a Balance which has an Stakeholder's ID as a field.
I need to relate these two classes, Balance and Stakeholder, but I don't know what's the proper way. I've seen there's a field that could be appropiate but I still don't understand it: EmbeddedDocumentField()
class Balance(Document):
id = UUIDField()
creation_date = DateTimeField(auto_now_on_insert=True)
gross_balance = FloatField(required=True, min_value=0, default=0)
balances_description = StringField(required=True, max_length=255)
stake_holder = #FK to Stakeholder
class Stakeholder(Document):
...
Any idea?
If Stakeholder represents document from other collection and stake_holder is ObjectId, you should use ReferenceField()
stake_holder = ReferenceField(reference_document_type=Stakeholder)

Deleting from many-to-many SQL-Alchemy and Postgresql

I'm trying to delete a child object from a many-to-many relationship in sql-alchemy.
I keep getting the following error:
StaleDataError: DELETE statement on table 'headings_locations' expected to delete 1 row(s); Only 2 were matched.
I have looked at a number of the existing stackexchange questions
(SQLAlchemy DELETE Error caused by having a both lazy-load AND a dynamic version of the same relationship, SQLAlchemy StaleDataError on deleting items inserted via ORM sqlalchemy.orm.exc.StaleDataError, SQLAlchemy Attempting to Twice Delete Many to Many Secondary Relationship, Delete from Many to Many Relationship in MySQL)
regarding this as well as read the documentation and can't figure out why it isn't working.
My code defining the relationships is as follows:
headings_locations = db.Table('headings_locations',
db.Column('id', db.Integer, primary_key=True),
db.Column('location_id', db.Integer(), db.ForeignKey('location.id')),
db.Column('headings_id', db.Integer(), db.ForeignKey('headings.id')))
class Headings(db.Model):
__tablename__ = "headings"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
version = db.Column(db.Integer, default=1)
special = db.Column(db.Boolean(), default=False)
content = db.relationship('Content', backref=db.backref('heading'), cascade="all, delete-orphan")
created_date = db.Column(db.Date, default=datetime.datetime.utcnow())
modified_date = db.Column(db.Date, default=datetime.datetime.utcnow(), onupdate=datetime.datetime.utcnow())
def __init__(self, name):
self.name = name
class Location(db.Model):
__tablename__ = "location"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
account_id = db.Column(db.Integer, db.ForeignKey('account.id'))
version = db.Column(db.Integer, default=1)
created_date = db.Column(db.Date, default=datetime.datetime.utcnow())
modified_date = db.Column(db.Date, default=datetime.datetime.utcnow())
location_prefix = db.Column(db.Integer)
numbers = db.relationship('Numbers', backref=db.backref('location'), cascade="all, delete-orphan")
headings = db.relationship('Headings', secondary=headings_locations,
backref=db.backref('locations', lazy='dynamic', cascade="all"))
def __init__(self, name):
self.name = name
And my delete code is as follows:
#content_blueprint.route('/delete_content/<int:location_id>/<int:heading_id>')
#login_required
def delete_content(location_id, heading_id):
import pdb
pdb.set_trace()
location = db.session.query(Location).filter_by(id = location_id).first()
heading = db.session.query(Headings).filter_by(id = heading_id).first()
location.headings.remove(heading)
#db.session.delete(heading)
db.session.commit()
flash('Data Updated, thank-you')
return redirect(url_for('content.add_heading', location_id=location_id))
Whichever way i try and remove the child object (db.session.delete(heading) or location.headings.remove(heading) I still get the same error.
Any help is much appreciated.
My database is postgresql.
Edit:
My code which adds the relationship:
new_heading = Headings(form.new_heading.data)
db.session.add(new_heading)
location.headings.append(new_heading)
db.session.commit()
I would assume that the error message is correct: indeed in your database you have 2 rows which link Location and Heading instances. In this case you should find out where and why did this happen in the first place, and prevent this from happening again
First, to confirm this assumption, you could run the following query against your database:
q = session.query(
headings_locations.c.location_id,
headings_locations.c.heading_id,
sa.func.count().label("# connections"),
).group_by(
headings_locations.c.location_id,
headings_locations.c.heading_id,
).having(
sa.func.count() > 1
)
Assuming, the assumption is confirmed, fix it by manually deleting all the duplicates in your database (leaving just one for each).
After that, add a UniqueConstraint to your headings_locations table:
headings_locations = db.Table('headings_locations',
db.Column('id', db.Integer, primary_key=True),
db.Column('location_id', db.Integer(), db.ForeignKey('location.id')),
db.Column('headings_id', db.Integer(), db.ForeignKey('headings.id')),
db.UniqueConstraint('location_id', 'headings_id', name='UC_location_id_headings_id'),
)
Note that you need to need to add it to the database, it is not enough to add it to the sqlalchemy model.
Now the code where the duplicates are inserted by mistake will fail with the unique constraint violation exception, and you can fix the root of the problem.

How can I use the same ObjectId for 2 collections using MongoEngine ReferenceField?

I have a fairly heavy User document class, and I would like to cut it in two pieces: the user's profile (name and avatar) in a UserProfile document and the rest in a User document, like this (using MongoEngine):
from mongoengine import *
class User(Document):
login = StringField()
password = StringField()
posts = ListField(ReferenceField("Post", dbref = False))
#... a bunch of other fields
class UserProfile(Document):
name = StringField()
avatar = URLField()
I would like to have the same ObjectId for both the UserProfile and the User, so that I only need one ObjectId to reference both the User and the UserProfile. After all, it's really a one-to-one relationship, and since a user can author many posts, I don't want to embed her profile in the posts themselves. When creating a user document, I would immediately create the corresponding profile document like this:
john = User.objects.create(login = "john", password = "super!password")
john_profile = UserProfile.objects.create(id = john.id, name = "John Smith",
avatar = "http://www.example.com/img/photo.jpg")
So far so good. Now I have a Post document with an author field that references the User document:
class Post(Document):
author = ReferenceField("User", dbref = False)
text = StringField()
I would like to add an author_profile reference, based on the same ObjectId. I tried this:
class Post(Document):
author = ReferenceField("User", dbref = False)
author_profile = ReferenceField("User", db_field = "author", dbref = False)
text = StringField()
But I get the following exception:
mongoengine.base.InvalidDocumentError: Multiple db_fields defined for: author
So it seems that I have to do so "manually". Something like this perhaps:
class Post(Document):
author = ReferenceField("User", dbref = False)
text = StringField()
#property
def author_profile(self):
if hasattr(self, "_author_profile"):
return self._author_profile
self._author_profile = UserProfile.objects.get(id = self._data["author"].id)
return self._author_profile
I guess it's not that bad, but isn't there a better solution?
Thanks.
Note: I read the mongodb documentation about one-to-one relationships, as well as the mongoengine ReferenceField documentation, but it did not help me on this specific question.
You'd have to store the same id twice to do this:
class Post(Document):
author = ReferenceField("User", dbref = False)
author_profile = ReferenceField("UserProfile", dbref = False)
text = StringField()
I'm not sure if that brings any benefits to your solution - there maybe an improvement in the number of queries to dereference, but I'd have to test that!
I ended up writing this:
def user_profile(reference_name):
attrib_name = "_" + reference_name + "_profile"
def user_profile_getter(self):
if not hasattr(self, attrib_name):
reference = self._data.get(reference_name, None)
if not reference:
return None
setattr(self, attrib_name, UserProfile.objects.get(reference.id))
return getattr(self, attrib_name)
return property(user_profile_getter)
My Post class now looks like this:
class Post(Document):
author = ReferenceField("User", dbref = False)
author_profile = user_profile("author")
text = StringField()
Whenever I add a ReferenceField that points to the User class, I also add such a user_profile (read-only) reference. Note that if you only access the author_profile, it will not load the author, and vice versa.

Entity Framework the use of reference

I use
p.AuthorsReference.EntityKey = new System.Data.EntityKey("PetitionsContainer.Authors", "Id", authorId);
but I get entities in PetitionsContainer.Questions participate in the QuestionAuthor relationship.
0 related 'Author' were found. 1 'Author' is expected.
Now, the Author with the Id authorId is already in the database.
It is true that each question must have 1 author.
Though, can't I use AuthorsReference instead of something like p.Authors.Add(new Author())?
If you set up the reference you must also fill the author. You can try using this:
// Attach a dummy author to the context so that context believes that you
// loaded the author from the database
Author a = new Author { Id = authorId };
context.Authors.Attach(a);
// Now assign existing author to the question
question.Author = a;