How to control field name for related model fetching in Peewee? - python

I have two models: User and Message. Each Message has two references to User (as sender and as receiver). Also I have defined an other_user hybrid method for Message which returns "user other than specific one" - see below:
from peewee import *
from playhouse.hybrid import hybrid_method
from playhouse.shortcuts import case
class User(Model):
name = CharField()
class Message(Model):
sender = ForeignKeyField(User, related_name='messages_sent')
receiver = ForeignKeyField(User, related_name='messages_received')
text = TextField()
#hybrid_method
def other_user(self, user):
if user == self.sender:
return self.receiver
elif user == self.receiver:
return self.sender
else:
raise ValueError
#other_user.expression
def other_user(cls, user):
return case(user.id, (
(cls.sender, cls.receiver),
(cls.receiver, cls.sender)))
Now I want to make a composite query which will retrieve all messages for current user and also retrieve information about "other" user than current. Here is how I do it:
current_user = request.user # don't matter how I retrieve it
query = (Message.select(Message, User)
.where(
(Message.sender == current_user) |
(Message.receiver == current_user))
.join(User, on=(User.id == Message.other_user(current_user))))
This query works well - i.e. it retrieves the exact information I need.
But here is the problem: "other user" information is always saved as sender field.
If I use this with models which have no direct ForeignKey reference then peewee creates a new field (in this case it would be named user) for additional requested model. But if there is at least one ForeignKey relationship from primary model to secondary requested model then it uses first such relationship.
Is it possible to somehow override this behaviour?
I tried Model.alias() method, but it (unlike Node.alias) doesn't allow to specify name.

I'm not completely sure what you want, so I'll provide a snippet that will likely work for your specific scenario, and will hopefully also allow you to learn how to do what you want, if this isn't it:
SenderUser = User.alias()
ReceiverUser = User.alias()
OtherUser = User.alias()
query = (Message.select(Message, SenderUser, ReceiverUser)
.where(
(Message.sender == current_user) |
(Message.receiver == current_user))
.join(SenderUser, on = (Message.sender == SenderUser.id).alias('sender'))
.switch(Message)
.join(ReceiverUser, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
.switch(Message)
.join(OtherUser, on = (Message.other_user(current_user) == OtherUser.id).alias('other_user'))
Notes:
You don't really need to create all those aliases (SenderUser/ReceiverUser/OtherUser), just two, and use User for the other. I just find that the query becomes more readable like this.
When you define an alias in the on clause, you basically tell peewee in which variable to store the joined table. I'm sending them directly to the already existing properties (sender/receiver). Also, I'm creating an extra property in the model with the value of the other user, which you can access as usual with self.other_user.
That switch method switches the current context to Message, so you can join a new table to Message instead of the SenderUser/ReceiverUser contexts where you end up after the two first joins.
If for some reason you're joining something that might be undefined (which doesn't seem to be the case here as both users are likely mandatory), you would probably want to add that you want a left outer join, like this:
.join(ReceiverUser, JOIN.LEFT_OUTER, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
Don't forget to from peewee import JOIN
Something else I just noticed, is that you likely want to change that other_user method you have to compare ids instead of the model variables. If self.sender is not filled when you access it, peewee will trigger a database select to get it, so your other_user method possibly triggers 2 select queries. I would do it like:
#hybrid_method
def other_user_id(self, user):
if user.id == self.sender_id:
return self.receiver_id
elif user.id == self.receiver_id:
return self.sender_id
else:
raise ValueError
You can see that I use sender_id instead of sender.id. That uses the ids for each foreign key that are already set in the message model. If you did self.receiver.id you would likely trigger that select anyway, to then access the id property (I'm not 100% sure here though).

Related

Django fake model instanciation - No testunit [duplicate]

This question already has an answer here:
How can I create a django model instance with deferred fields without hitting the database?
(1 answer)
Closed 8 months ago.
I want to know if I can instanciate an empty fake model just with id of database record.
I found way to create mockup model, but I want a production-friendly solution.
Explanation of my issue :
I want to list users settings for users who choose to be displayed on public mode :
user_displayed_list = UserPublicProfile.objects.filter(
displayed = True,
).only(
'user_id',
'is_premium',
)
user_settings_list = []
for user_displayed in user_displayed_list:
# I have to send user Instance to the next method :
user_settings = self.get_user_settings(user_displayed.user)
user_settings_list.append(user_settings)
# But ’user_displayed.user’ run an new SQL query
I know I can improve my queryset as :
user_displayed_list = UserPublicProfile.objects.filter(
displayed = True,
).select_related(
'user'
).only(
'user',
'is_premium',
)
But It makes an useless join because I need only the user id field in get_user_settings():
The get_user_settings() method (it could help to understand context):
def get_user_settings(self, user)
user_settings = UserSettings.objects.get(user = user)
return user_settings
In real project, this method run more business feature
Is there a way to instanciate a User model instance with only id field filled ?
I don't want to use a custom empty class coded for this purpose. I really want an object User.
I didn't find anything for that. If it's possible, I could use it by this way :
for user_displayed in user_displayed_list:
FakeUser = User.objects.create_fake(id = user_displayed.user_id)
# I have to send user Instance to the next method :
user_settings = self.get_user_settings(FakeUser)
Without seeing the complete models, I'm assuming a bit. Assuming that UserSettings has a ForeignKey to User. Same for UserPublicProfile. Or User has ForeignKey to UserSettings. Works as well.
Assuming that, I see two solutions.
Solution #1; use the ORM to full potential
Just saw your comment about the 'legacy method, used many times'.
Django relations are very smart. They accept either the object or the ID of a ForeignKey.
You'd imagine this only works with a User. But if you pass the id, Django ORM will help you out.
def get_user_settings(self, user)
user_settings = UserSettings.objects.get(user = user)
return user_settings
So in reality, these work the same:
UserSettings.objects.get(user=1)
UserSettings.objects.get(user_id=1)
Which means this should work, without a extra query:
user_displayed_list = UserPublicProfile.objects.filter(
displayed = True,
).only(
'user_id',
'is_premium',
)
user_settings_list = []
for user_displayed in user_displayed_list:
# I have to send user Instance to the next method :
user_settings = self.get_user_settings(user_displayed.user_id) # pass the user_id instead of the object.
user_settings_list.append(user_settings)
Solution #2: chain relations
Another solution, again, still assuming quite a bit ;)
It would think you can chain the model together.
Assuming these FK exists: UserPublicProfile -> User -> UserSetting.
You could do this:
user_displayed_list = UserPublicProfile.objects.filter(
displayed = True,
).select_related(
'user', 'user__usersettings', # depends on naming of relations
).only(
'user',
'is_premium',
)
for user_displayed in user_displayed_list:
# I have to send user Instance to the next method :
user_settings = user_displayed.user.usersettings # joined, so should cause no extra queries. Depends on naming of relations.
user_settings_list.append(user_settings)

How to let a Reference Field accept multiple Document schemas in MongoEngine?

Context: I am writing an API (using Flask and MongoEngine) with multiple account types, including perhaps buildings. I need the database to hold some temporary accounts until a particular building registers.
This is how I've been referencing just one type of user:
current_holder_of_stuff = ReferenceField(ActiveUser)
I know GenericReferenceField is also an option, but what if I only want to allow two types of references? Is there anything like:
current_holder_of_stuff = ReferenceField(ActiveUser, TempUser)
Muchos thankos!
It may work to create a parent class of type User and then have inherited classes of ActiveUser and TempUser to deal with the various user types. As for the requirement for current_holder_of_stuff to be two possible document types, you cannot use a single reference field. As you've dismissed using GenericReferenceField then one way might be to add a property method and a StringField with options such as this:
import mongoegine as mdb
class User(mdb.Document):
name = mdb.StringField()
meta = {'allow_inheritance': True}
class ActiveUser(User):
activation_date = mdb.DateTimeField()
class TempUser(User):
date_limit = mdb.DateTimeField()
class Building(mdb.Document):
address = mdb.StringField()
class Stuff(mdb.Document):
user = mdb.ReferenceField(User)
building = mdb.ReferenceField(Building)
currently_with = mdb.StringField(options=['user','building'],required=True)
#property
def current_holder_of_stuff(self):
if self.currently_with == "user":
return self.user
else:
return self.building
You can also use mongoengine's signals to perform checks pre-save to ensure there is only a user or building defined.

Fetch and save a copy of a QuerySet with a new User field in Django

I have a number of models, each with a user field. Each time a new user is registered I want to copy the instances of these models which have user.id = 1, and save them with a new user id in the user field. I've read that a copy of a model instance can be made by setting the pk to None so this is what I've tried to do first. I'm trying to do this on a QuerySet since there are many instances for some tables (over 1000 for some models, though mostly less than 100).
def copy_tables(user):
# This approach fails with "Voice has no field named 'pk'"
voice = Voice.objects.filter(user=1).update(
pk=None, user=user.id, right_answers=0, wrong_answers=0)
I've also tried using copy.deepcopy(Voice.objects.filter(user=1)) but calling update() on this is also still changing the original table in the database.
def copy_tables(user):
copy.deepcopy(Voice.objects.filter(user=1)).update(
user=user.id, right_answers=0, wrong_answers=0)
# This test throws an error because the original models have been changed
assert Voice.objects.filter(user=1).first()
Am I missing some other way of doing this? Also, I thought that all model instances had a primary key, pk, which is automatically set by Django. Is this not the case?
When calling the save method on a model instance, Django determines if it must be inserted or updated by checking if it has a primary key (ref.).
So if you set the PK to None on a model instance, Django will trying adding it as a new row in the database.
Demo:
# Retrieve the object belonging to the user whose ID is 1.
# Note that you cannot use "user=1", you need to tell Django you
# are querying by id, using "user__id=1" .
obj = MyModel.objects.get(user__id=1)
# Set PK to None so that Django thinks it is not save in the database.
obj.pk = None
# Save to insert it.
obj.save()
In your case where you have multiple objects, you can use bulk_create to gain in performances:
voices = Voice.objects.filter(user__id=1)
new_voices = []
for voice in voices:
voice.pk = None
voice.user = user
right_answsers = 0
wrong_anwsers = 0
new_voices.append(voice)
Voice.objects.bulk_create(new_voices)

How to select and limit the related_name connection in the Peewee ORM?

I'm using Flask with the Peewee ORM in which I have defined two tables like so:
class Ticket(db.Model):
created = DateTimeField(default=datetime.now)
customer_uuid = CharField() # the customer's UUID gotten from App. More info comes from bunq API.
ticket_type = ForeignKeyField(TicketType, related_name='tickets')
active = BooleanField(default=True)
class Assign(db.Model):
created = DateTimeField(default=datetime.now)
ticket = ForeignKeyField(Ticket, related_name='assigned_to')
user = ForeignKeyField(User, related_name='assigned_tickets')
In the Assign table, several users can be assigned to a ticket, but only the last one counts (i.e., if a new user gets assigned, the previous ones should be disregarded). So I select the active tickets using the following:
open_tickets = Ticket.select().where(Ticket.active == True)
I now want to use this loop in my template. With every iteration however, I also want to display the assigned user. But open_ticket[0].assigned_to obviously returns several assignments, and with it several users.
Would anybody know how I can get the latest assigned user for every ticket within a loop?
This worked for me in Sqlite:
q = (Ticket
.select(Ticket, Assign, User)
.join(Assign)
.join(User)
.group_by(Ticket)
.order_by(Ticket.id, Assign.id.desc()))
for ticket in q:
print ticket.id, ticket.assign.user.username

Google App Engine Python Datastore

Basically what Im trying to make is a data structure where it has the users name, id, and datejoined. Then i want a "sub-structure" where it has the users "text" and the date it was modified. and the user will have multiple instances of this text.
class User(db.Model):
ID = db.IntegerProperty()
name = db.StringProperty()
datejoined = db.DateTimeProperty(auto_now_add=True)
class Content(db.Model):
text = db.StringProperty()
datemod= db.DateTimeProperty(auto_now_add = True)
Is the code set up correctly?
One problem you will have is that making User.ID unique will be non-trivial. The problem is that two writes to the database could occur on different shards, both check at about the same time for existing entries that match the uniqueness constraint and find none, then both create identical entries (with regard to the unique property) and then you have an invalid database state. To solve this, appengine provides a means of ensuring that certain datastore entities are always placed on the same physical machine.
To do this, you make use of the entity keys to tell google how to organize the entities. Lets assume you want the username to be unique. Change User to look like this:
class User(db.Model):
datejoined = db.DateTimeProperty(auto_now_add=True)
Yes, that's really it. There's no username since that's going to be used in the key, so it doesn't need to appear separately. If you like, you can do this...
class User(db.Model):
datejoined = db.DateTimeProperty(auto_now_add=True)
#property
def name(self):
return self.key().name()
To create an instance of a User, you now need to do something a little different, you need to specify a key_name in the init method.
someuser = User(key_name='john_doe')
...
someuser.save()
Well, really you want to make sure that users don't overwrite each other, so you need to wrap the user creation in a transaction. First define a function that does the neccesary check:
def create_user(username):
checkeduser = User.get_by_key_name(username)
if checkeduser is not None:
raise db.Rollback, 'User already exists!'
newuser = User(key_name=username)
# more code
newuser.put()
Then, invoke it in this way
db.run_in_transaction(create_user, 'john_doe')
To find a user, you just do this:
someuser = User.get_by_key_name('john_doe')
Next, you need some way to associate the content to its user, and visa versa. One solution is to put the content into the same entity group as the user by declaring the user as a parent of the content. To do this, you don't need to change the content at all, but you create it a little differently (much like you did with User):
somecontent = Content(parent=User.get_by_key_name('john_doe'))
So, given a content item, you can look up the user by examining its key:
someuser = User.get(somecontent.key().parent())
Going in reverse, looking up all of the content for a particular user is only a little trickier.
allcontent = Content.gql('where ancestor is :user', user=someuser).fetch(10)
Yes, and if you need more documentation, you can check here for database types and here for more info about your model classes.
An alternative solution you may see is using referenceproperty.
class User(db.Model):
name = db.StringProperty()
datejoined = db.DateTimeProperty(auto_now_add=True)
class Content(db.Model):
user = db.ReferenceProperty(User,collection_name='matched_content')
text = db.StringProperty()
datemod= db.DateTimeProperty(auto_now_add = True)
content = db.get(content_key)
user_name = content.user.name
#looking up all of the content for a particular user
user_content = content.user.matched_content
#create new content for a user
new_content = Content(reference=content.user)

Categories