I am attempting to model a friendship using SQLAlchemy ORM. The relationship that I am trying to model is symmetric. Similar to Facebook, if user a is to add user b, user b must approve that friendship request. My current model is as follows.
class User(db.Model):
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(35), unique=False)
username = db.Column(db.String(25), index=True, unique=True)
password = db.Column(db.String(35), unique=False)
email = db.Column(db.String(35), unique=True)
phone_number = db.Column(db.String(22))
# define relationships
requester = db.relationship('Relationship', foreign_keys='Relationship.requesting_user', backref='requester')
receiver = db.relationship('Relationship', foreign_keys='Relationship.receiving_user', backref='received')
def __repr__(self):
return '<User %r>' % (self.username)
class Relationship(db.Model):
__tablename__ = 'Relationship'
id = db.Column(db.Integer, primary_key=True)
requesting_user = db.Column(db.Integer, db.ForeignKey('User.id'))
receiving_user = db.Column(db.Integer, db.ForeignKey("User.id"))
status = db.Column(db.Integer)
__table_args__ = (db.UniqueConstraint('receiving_user', 'requesting_user', name='_receiving_user_uc'), )
The model works, however, I don't think that it is properly modeled. Is it even required that I use a status? I'm assuming it can be modeled so that each friend relationship gets its own entry. Currently, a user can initiate a friend request with another user. When the other user approves the request, the status changes to accepted. I have looked a little into association tables but am not too sure how they would play into a model like this. Any advice on my current model and how it can be improved would be greatly appreciated.
Among other things, you may want to learn about association proxies. An association proxy tells SQLAlchemy that you have a many-to-many relationship mediated by an intermediate table which may contain additional data. In your case, each User can send multiple requests and also receive multiple requests and Relationship is the mediating table which contains the status column as additional data.
Here is a variant of your code which stays relatively close to what you wrote:
from sqlalchemy.ext.associationproxy import association_proxy
class User(db.Model):
__tablename__ = 'User'
# The above is not necessary. If omitted, __tablename__ will be
# automatically inferred to be 'user', which is fine.
# (It is necessary if you have a __table_args__, though.)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(35), unique=False)
# and so forth
requested_rels = db.relationship(
'Relationship',
foreign_keys='Relationship.requesting_user_id',
backref='requesting_user'
)
received_rels = db.relationship(
'Relationship',
foreign_keys='Relationship.receiving_user_id',
backref='receiving_user'
)
aspiring_friends = association_proxy('received_rels', 'requesting_user')
desired_friends = association_proxy('requested_rels', 'receiving_user')
def __repr__(self):
# and so forth
class Relationship(db.Model):
# __tablename__ removed, becomes 'relationship'
# __table_args__ removed, see below
requesting_user_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
receiving_user_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
# Marking both columns above as primary_key creates a compound primary
# key, which at the same time saves you the effort of defining the
# UNIQUE constraint in __table_args__
status = db.Column(db.Integer)
# Implicit one-to-many relations: requesting_user, receiving_user.
# Normally it would be more convenient to define those relations on
# this side, but since you have two outgoing relationships with the
# same table (User), you chose wisely to define them there.
(Note how I ordered the lines slightly differently and how I used the _id suffix for foreign key columns while reserving the same name without the suffix for the corresponding db.relationships. I would suggest that you adopt this style, too.)
Now you have a clean way to access incoming and outgoing friendship requests as well as the corresponding users directly from your User model. However, this is still less than ideal because you need to write the following code in order to get all confirmed friends of a user:
def get_friends(user):
requested_friends = (
db.session.query(Relationship.receiving_user)
.filter(Relationship.requesting_user == user)
.filter(Relationship.status == CONFIRMED)
)
received_friends = (
db.session.query(Relationship.requesting_user)
.filter(Relationship.receiving_user == user)
.filter(Relationship.status == CONFIRMED)
)
return requested_friends.union(received_friends).all()
(I did not test this; you might need to also join with User in both queries in order for the union to work.)
To make things worse, the model name Relationship as well as the names of several members within the models don't seem to convey very well what they actually mean.
You can improve matters by removing Relationship.status and renaming Relationship to FriendshipRequest. Then, add a second User-to-User association model called Friendship and add a corresponding second set of db.Relationships with backrefs and association_proxys to User. When somebody sends a friendship request, you file a record to FriendshipRequest. If the request is accepted, you remove the record and replace it with a new record in Friendship. This way, instead of using a status code, the status of a friendship is encoded by the table in which you store a pair of users. The Friendship model may look like this:
class Friendship(db.Model):
user1_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
user2_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
# Implicit one-to-many relations: user1, user2
# (defined as backrefs in User.)
(Corresponding db.relationships and association_proxys in User are left as an exercise to the reader.)
This approach saves you half of the filtering operations when you need the confirmed friends of a user. Still, you need to make a union of two queries because your user can be either user1 or user2 in each instance of Friendship. This is inherently difficult because we are dealing with a reflexive symmetric relationship. I think it is possible to invent still more elegant ways to do it, but I think that would be complicated enough to warrant a new question here on Stack Overflow.
Related
I'm creating a small-ish django application using AllAuth for the authetncation, which I have customised myself to include some additional fields.
Part of the sites functionality requires me to refrence the logged in user through a Foreign Key multiple times and I'm approaching this through the custom model I created, called UserProfile; (I tried Django's User model & this also gave me errors)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
phone_number = models.CharField(max_length=30, null=False)
postcode = models.CharField(max_length=500, null=True, blank=True)
town_or_city = models.CharField(max_length=40, null=True, blank=True)
street_address1 = models.CharField(max_length=80, null=True, blank=True)
street_address2 = models.CharField(max_length=80, null=True, blank=True)
county = models.CharField(max_length=80, null=True)
I'm referencing the above model in the activity table:
class Activity(models.Model):
class Meta:
verbose_name_plural = 'Activities'
activity_id = models.CharField(max_length=32, null=False, editable=False)
host = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
name = models.CharField(max_length=254, null=False, blank=False)
date = models.DateField()
start_time =models.TimeField()
end_time = models.TimeField()
duration = models.DurationField(blank=True, null=True)
location = models.CharField(max_length=40, null=False, blank=False)
description = models.CharField(max_length=140, null=False, blank=False)
available = models.BooleanField(default=True)
Everything works smoothly, as I can use this to create one activity per user, however I want to make this a Many to One field, which required me to update from settings.AUTH_USER_MODEL to the Foreign Key.
Unfortunately, I'm getting the following error:
FOREIGN KEY constraint failed
when I try to add a new activity now, and I'm at a loss as to why this is happening.
If someone could point me in the right direction on how to create this many to one functionality, it would be great.
I can't see your whole code so the answear may be wrong but it Looks like you dont have Primary Key in UserProfile
ForeignKey As Default takes Primary Key as argument
Potential solve:
class Activity(models.Model):
host = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile', primary_key=True)
If it dosent solve your problem please put full error message
DOCS
I would guess, from what information that you've provided, is that what has happened is that you've potentially got a user who doesn't have a user profile, so when you try to create the activity it fails with the error you specified. On the other hand, it could also be related to how you're rendering and validating your forms. Furthermore it could also be due to the way you are using class based views views or function based views.
In any case, please provide all information possible. Generally all information is required, not just the part you're unsure of.
How do you create an activity?
How do you validate your forms?
How is the user profile ID populated?
Providing more information, I will happily respond as it seems like a straight forward question
I am attempting to optimize some code. I have model with many related models, and I want to annotate and filter by the value of a field of a specific type of these related models, as they are designed to be generic. I can find all instances of the type of related model I want, or all of the models related to the parent, but not the related model of the specific type related to the parent. Can anyone advise?
I initially tried
parents = parent.objects.all()
parents.annotate(field_value=Subquery(related_model.objects.get(
field__type='specific',
parent_id=OuterRef('id'),
).value)))
But get the error This queryset contains a reference to an outer query and may only be used in a subquery. When I tried
parents = parent.objects.all()
parents.annotate(field_value=Q(related_model.objects.get(
field__type='specific',
parent_id=F('id'),
).value)))
I get DoesNotExist: related_field matching query does not exist. which seems closer but still does not work.
Model structure:
class parent(models.Model):
id = models.IntegerField(null=False, primary_key=True)
class field(models.Model):
id = models.IntegerField(null=False, primary_key=True)
type = models.CharField(max_length=60)
class related_model(models.Model):
parent = models.ForeignKey(parent, on_delete=models.CASCADE, related_name='related_models')
field = models.ForeignKey(field, on_delete=models.CASCADE, related_name='fields')
Is what I want to do even possible?
Never mind I decided to do a reverse lookup, kinda like
parent_ids = related_model.objects.filter(field__type='specific', parent_id__in=list_of_parents).values_list('parent_id')
parents.objects.filter(id__in=parents_id)
Pretty new to working with databases in this way. I've got some sample code below. I've got the instrument object which will be a db listing of types of instruments, guitar, piano etc. Then the user object will have a ManyToMany on that so each user can have as many of those listed in their profile as they play.
What I'm stuck on is I'd like to have a field for experience with each of those instruments. Just not sure how to accomplish this without just static fields for how many instruments there would be (which since it's modifiable, could change). Thanks for any pointing in the correct direction.
class Instrument(models.Model):
# Name of the instrument
name = models.CharField(_('Name of Instrument'), blank=True, max_length=255)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
#python_2_unicode_compatible
class User(AbstractUser):
# First Name and Last Name do not cover name patterns
# around the globe.
name = models.CharField(_('Name of User'), blank=True, max_length=255)
zipcode = models.IntegerField(_('Zip Code of the User'), blank=True, null=True)
instruments = models.ManyToManyField(Instrument)
Seems like a textbook use case for a through model with extra fields.
class InstrumentExperience(models.Model):
user = models.ForeignKey('User')
instrument = models.ForeignKey('Instrument')
experience = models.CharField(max_length=100)
class User(AbstractUser):
...
instruments = models.ManyToManyField('Instrument', through='InstrumentExperience')
I have the following 2 tables
In models.py
class Foo(models.Model):
uuid = models.CharField(_('UUID'), primary_key=True, default=uuid4)
and
class FooExt(models.Model):
uuid = models.ForeignKey(Foo, verbose_name=_('UUID'), primary_key=True)
time = models.DateTimeField(_('Create DateTime'), auto_now_add=True)
Basically, I have Foo and FooExt. I want a one-to-one relation between FooExt. That's why I set FooExt's primary key to be foreign key into Foo (not sure if this is the right thing to do).
Now I add an entry into Foo. Does an entry for FooExt automatically get created? Or do I need to manually add an entry to both Foo and FooExt?
Is there anything I can do to get the "automatic" add feature? Conceptually, these 2 tables describe the same thing, but I just don't want to pollute Foo with extra information. So it'd be great if an add to Foo automatically creates a corresponding FooExt.
If you want an OneToOne relation, then use models.OneToOneField instead of models.ForeignKey. with foreign keys you will need add unique=True in you ForeignKey:
class Foo(models.Model):
uuid = models.CharField(_('UUID'), primary_key=True, default=uuid4)
class FooExt(models.Model):
uuid = models.OneToOneField(Foo, verbose_name=_('UUID'), primary_key=True)
time = models.DateTimeField(_('Create DateTime'), auto_now_add=True)
No, an entry for FooExt don't get created when you create a Foo instance, you need to manually add an entry to both Foo and FooExt. think in Places and Restaurants, many places can be restaurants, but no all the places are restaurants.
if you like an automatic add feature inside Foo that create a FooExt instance, then you can overload the save method inside Foo that create and save FooExt instance too, something like this:
class Foo(models.Model):
....
....
def save(self, *args, **kwargs):
super(Foo, self).save(*args, **kwargs)
foo_ext = FooExt()
foo_ext.uuid = self
foo_ext.save()
Looks like there was mistake in Yonsy Solis answer in save method(corrected), try this:
class Foo(models.Model):
....
....
def save(self, *args, **kwargs):
super(Foo, self).save(*args, **kwargs)
foo_ext = FooExt()
foo_ext.uuid = self
foo_ext.save()
remark: i cant comment yet, so i decide to create answer
Have a look at the AutoOneToOneField in django-annoying
https://github.com/skorokithakis/django-annoying
or answer to this question: Can Django automatically create a related one-to-one model?
lets say I have a model named User, and other models representing actions done by the a user, like "User_clicked", "User_left", "User_ate_cookie" etc. etc.
the User_* models have different fields and inherit from a single abstract class (User_do_something)
my question is this:
what's the django-way of querying ALL the models, from all the relevant tables, that point on a specific user?
eg. I want to do User.get_all_actions() and get a list with mixed type of models in them, containing all the objects that inherit from User_do_something and point on the specific user.
Note: performance is critical. I don't want to make multiple db queries and combine the list, if possible, I want to do it with a single sql select.
some code to be clear:
class User(models.Model):
uuid = models.UUIDField(unique=True, default=uuid.uuid4)
creation_time = models.DateTimeField(auto_now_add=True)
def get_all_actions(self):
'''
return a list with ALL the actions this player did.
'''
??????? how do I do this query ???????
class User_do_action(models.Model):
user = models.ForeignKey(User)
creation_time = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class User_click(User_do_action):
... some fields
class User_left(User_do_action):
... some fields
class User_ate_cookie(User_do_action):
... some fields
etc. etc.
thanks!