Require ForeignKey object to be a related object of another model - python

I have a model
class EventArticle(models.Model):
event = models.ForeignKey(Event, related_name='article_event_commentary')
author = models.ForeignKey(Person)
and another model
class Event(models.Model):
attendies = models.ManyToManyField(Person, related_name="people")
How do I restrict an author to only objects that are also attendies?

Typically, the ForeignKey limit_choices_to argument is your friend in situations like this. (See https://docs.djangoproject.com/en/1.8/ref/models/fields/#django.db.models.ForeignKey.limit_choices_to)
You could restrict the author to a list of users of have attended any event. However, even doing that is far from ideal:
# don't try this at home, this will be excruciatingly slow...
def author_options():
all_attendee_ids = []
for e in Event.objects.all():
for a in e.attendies.all():
all_attendee_ids.append(a.id)
return {'author': Person.objects.filter(id_in=set(all_attendee_ids)).id}
# requires Django 1.7+
class EventArticle(models.Model):
event = models.ForeignKey(Event, related_name='article_event_commentary')
author = models.ForeignKey(Person, limit_choices_to=author_options)
However, even though you didn't explicitly state it in your question, I suspect you want authors to be limited to a set of attendees from that particular event, i.e. the same event as specified in the EventArticle model's event field.
In which case, this brings about two problems which I don't believe can be solved cleanly:
you can't pass parameters (i.e. the event ID) when using limit_choices_to, and
until the EventArticle model has a value for event, you wouldn't know which event was in question.
As using limit_choices_to isn't going to work here, there probably isn't a way to fix this cleanly. You could add a method to your EventArticle model which will give you a list of potential authors like so...
class EventArticle(models.Model):
event = models.ForeignKey(Event, related_name='article_event_commentary')
author = models.ForeignKey(Person)
def author_options(self):
if self.event:
return self.event.attendies.all()
else:
return []
...but you will still need to do some legwork to make those options available to the UI so the user can select them. Without knowing more about your setup, I'd be guessing if I tried to answer that part.

You can override save() of EventArticle model to ensure it.

Related

Is it possible to have unique together but in an order [duplicate]

So I have some models that look like this
class Person(BaseModel):
name = models.CharField(max_length=50)
# other fields declared here...
friends = models.ManyToManyField(
to="self",
through="Friendship",
related_name="friends_to",
symmetrical=True
)
class Friendship(BaseModel):
friend_from = models.ForeignKey(
Person, on_delete=models.CASCADE, related_name="friendships_from")
friend_to = models.ForeignKey(
Person, on_delete=models.CASCADE, related_name="friendships_to")
state = models.CharField(
max_length=20, choices=FriendshipState.choices, default=FriendshipState.pending)
So basically, I'm trying to model a Facebook-like friends situation, where there are different persons and one can ask any other person to be friends. That relationship is expressed through this last model Friendship.
So far so good. But there are three situations I'd like to avoid:
A Friendship can't have the same person in the friend_from and friend_to fields
Only one Friendship for a set of two Friends should be allowed.
The closest I've got to that, is adding this under the Friendship model:
class Meta:
constraints = [
constraints.UniqueConstraint(
fields=['friend_from', 'friend_to'], name="unique_friendship_reverse"
),
models.CheckConstraint(
name="prevent_self_follow",
check=~models.Q(friend_from=models.F("friend_to")),
)
]
This totally solves situation 1, avoiding someone to befriend with himself, using the CheckConstraint.
And partially solves situation number 2, because it avoids having two Friendships like this:
p1 = Person.objects.create(name="Foo")
p2 = Person.objects.create(name="Bar")
Friendship.objects.create(friend_from=p1, friend_to=p2) # This one gets created OK
Friendship.objects.create(friend_from=p1, friend_to=p2) # This one fails and raises an IntegrityError, which is perfect
Now there's one case that'd like to avoid that still can happen:
Friendship.objects.create(friend_from=p1, friend_to=p2) # This one gets created OK
Friendship.objects.create(friend_from=p2, friend_to=p1) # This one won't fail, but I'd want to
How would I make the UniqueConstraint work in this "two directions"? Or how could I add another constraint to cover this case?
Of course, I could overwrite the save method for the model or enforce this in some other way, but I'm curious about how this should be done at the database level.
So, just to sum up the discussion in the comments and to provide an example for anyone else looking into the same problem:
Contrary to my belief, this can not be achieved as of today, with Django 3.2.3, as pointed out by #Abdul Aziz Barkat. The condition kwarg that UniqueConstraint supports today isn't enough to make this work, because it just makes the constraint conditional, but it can't extend it to other cases.
The way of doing this in the future probably will be with UniqueConstraint's support for expressions, as commented by #Abdul Aziz Barkat too.
Finally, one way of solving this with a custom save method in the model could be:
Having this situation as posted in the question:
class Person(BaseModel):
name = models.CharField(max_length=50)
# other fields declared here...
friends = models.ManyToManyField(
to="self",
through="Friendship",
related_name="friends_to",
symmetrical=True
)
class Friendship(BaseModel):
friend_from = models.ForeignKey(
Person, on_delete=models.CASCADE, related_name="friendships_from")
friend_to = models.ForeignKey(
Person, on_delete=models.CASCADE, related_name="friendships_to")
state = models.CharField(
max_length=20, choices=FriendshipState.choices, default=FriendshipState.pending)
class Meta:
constraints = [
constraints.UniqueConstraint(
fields=['friend_from', 'friend_to'], name="unique_friendship_reverse"
),
models.CheckConstraint(
name="prevent_self_follow",
check=~models.Q(friend_from=models.F("friend_to")),
)
]
Add this to the Friendship class (that is, the "through" table of the M2M relationship):
def save(self, *args, symmetric=True, **kwargs):
if not self.pk:
if symmetric:
f = Friendship(friend_from=self.friend_to,
friend_to=self.friend_from, state=self.state)
f.save(symmetric=False)
else:
if symmetric:
f = Friendship.objects.get(
friend_from=self.friend_to, friend_to=self.friend_from)
f.state = self.state
f.save(symmetric=False)
return super().save(*args, **kwargs)
A couple of notes on that last snippet:
I'm not sure that using the save method from the Model class is the best way to achieve this because there are some cases where save isn't even called, notably when using bulk_create.
Notice that I'm first checking for self.pk. This is to identify when we are creating a record as opposed to updating a record.
If we are updating, then we will have to perform the same changes in the inverse relationship than in this one, to keep them in sync.
If we are creating, notice how we are not doing Friendship.objects.create() because that would trigger a RecursionError - maximum recursion depth exceeded. That's because, when creating the inverse relationship, it will also try to create its inverse relationship, and that one also will try, and so on. To solve this, we added the kwarg symmetric to the save method. So when we are calling it manually to create the inverse relationship, it doesn't trigger any more creations. That's why we have to first create a Friendship object and then separately call the save method passing symmetric=False.

Formset for MainClass<-ForeignKey<-OneToOneField?

I need to process applications to an amateur sports event. An event has several distances/subclasses, each of them has some restrictions (age, etc).
My models are
class Event(models.Model):
title = models.CharField(max_length=255)
# more fields
class Klass(models.Model):
title = models.CharField(max_length=255)
capacity = models.IntegerField()
event = models.ForeignKey('Event', related_name="klasses")
# more fields
class TeamRestrictions(models.Model):
age_min = models.IntegerField()
age_max = models.IntegerField()
klass = models.OneToOneField(TeamRestrictions, related_name='restrict')
# more fields
I want to have a single page where a user creates a new event: names it, adds several subclasses into it and restrictions for every subclass. Well, without this one-to-one relationship, for just Event with several Klasses, I could use FormSet.
Of course, I could move all TeamRestrictions fields to Klass, but that looks ugly for me.
What should I use for this more complex structure?
You should create for each model a form and do it separately or you can create really sofisticated form which will do it for you.
This form then would have fields as title (Event), title (Klass), capacity, event, age_min ... so for the relation fields as ForeignKey you will have to use the ChoiceField which will be populated with choices in the __init__ function and so on. Then it should have good cleaning function so that it would have olny valid data and at the end the save. You will have to look if user has selected a field or is creating a new one (such as Event for Klass) and then process them and link and create everything. But that's not the best solution (even it could be in one step) but it is a choice. It could look great even if you added some javascript.

What is the equivalent of None and Null in Django if statements [duplicate]

I have quite a simple problem to solve. I have Partner model which has >= 0 Users associated with it:
class Partner(models.Model):
name = models.CharField(db_index=True, max_length=255)
slug = models.SlugField(db_index=True)
user = models.ManyToManyField(User)
Now, if I have a User object and I have a Partner object, what is the most Pythonic way of checking if the User is associated with a Partner? I basically want a statement which returns True if the User is associated to the Partner.
I have tried:
users = Partner.objects.values_list('user', flat=True).filter(slug=requested_slug)
if request.user.pk in users:
# do some private stuff
This works but I have a feeling there is a better way. Additionally, would this be easy to roll into a decorator, baring in mind I need both a named parameter (slug) and a request object (user).
if user.partner_set.filter(slug=requested_slug).exists():
# do some private stuff
If we just need to know whether a user object is associated to a partner object, we could just do the following (as in this answer):
if user in partner.user.all():
#do something

Django: how to query relations effectively

I have a model "Booking" referencing to another model "Event" with a foreign key.
class Event(models.Model):
title = models.CharField(_("title"), max_length=100)
class Booking(models.Model):
user = models.ForeignKey('auth.User', ..., related_name='bookings')
event = models.ForeignKey('Event', ..., related_name='bookings')
I want to get all the events a user has booked in a set, to use it in a ListView.
I have managed to accomplish that by overwriting ListView's get_queryset method:
def get_queryset(self):
user_bookings = Booking.objects.filter(user=self.request.user)
events = Event.objects.filter(id__in=user_bookings.values('event_id'))
return events
But I am quite sure, that that is not a very efficient way of solving my problem, in terms of needed database queries.
I have thought about using "select_related" method, but I didn't figured out how I could benefit from that in my usecase.
My question is: How would you solve this? What is the most efficient way to do something like this?
You can do this in one line: Event.objects.filter(bookings__user=self.request.user)

Django model subclassing approaches

I'm designing a new Django app and due to several possibilities, I'm not sure which would be the best, thus I'd like opinions, and hopefully improve what I got so far.
This question comes close but not quite. This one touches the flat/nested subject which is helpful, while still not answering the question.
There are many others on the same subject, and yet none tell me what I want to know.
Background
The models have each unique properties with some shared attributes, and I need to reference them in another model, optimally with a single entry point rather than having a field for each possible model.
I want to be able to do complex Django ORM queries involving the Base class and filter by SubClass when needed. E.g Event.objects.all() to return all events. I'm aware of Django model utils Inheritance Manager and intend to use it if possible.
Also, I'll be using django admin to create and manage the objects, so an easy integration is a must. I want to be able to create a new SubEvent directly, without having first to create a Event instance.
Example
To illustrate, let's say I have the following models for app A.
class Event(models.Model):
commom_field = models.BooleanField()
class Meta:
abstract = True
class SubEventA(Event):
email = models.EmailField(unique=True)
class SubEventB(Event):
title = models.TextField()
class SubEventC(Event):
number = models.IntegerField(default=10)
# and so on
And also an app B, where I want to be able to reference a event which can be of any type, like:
class OtherModel(models.Model):
event = models.ForeignKey('A.Event')
# This won't work, because `A.Event` is abstract.
Possible solutions
Use a GenericForeignKey.
# B.models.py
class OtherModel(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
event = GenericForeignKey('content_type', 'object_id')
What I don't like about this is that I'll lose the querying capabilities Django ORM has, and I might need to do additional fiddling to get it working on admin. Not sure, never dealt with this before
Flatten Event
I can bring it all up to the base class and have flags or checks outside the model definition, something like:
class Event(models.Model):
commom_field = models.BooleanField()
email = models.EmailField(blank=True)
title = models.TextField(blank=True)
number = models.IntegerField(default=10)
This might seem like the best idea at first, but of course there are other kind of fields, and that forces me to allow nulls/blanks for most of them (like the email field), losing the db level integrity check.
OneToOne relationships
Rather than abstract like on 1 or flatten on 2 it is possible to have a db table for each, where the models will look like:
class Event(models.Model):
commom_field = models.BooleanField()
class SubEventA(models.Model):
event = models.OneToOneField(Event)
email = models.EmailField(unique=True)
class SubEventB(models.Model):
event = models.OneToOneField(Event)
title = models.TextField(blank=True)
class SubEventC(models.Model):
event = models.OneToOneField(Event)
number = models.IntegerField(default=10)
So far it solved the two initial problems, but now when I get to the admin interface, I'll have to customize each form to create the base Event before saving a SubEvent instance.
Questions
Is there a better approach?
Can any of the choices I present be improved in any direction (ORM query, DB constraints, admin interface)?
I've pondered about both answers and came up with something based off of those suggestions. Thus I'm adding this answer of my own.
I've chosen to use django-polymorphic, quite nice tool suggested by #professorDante. Since this is a multi-table inheritance, #albar's answer is also somewhat correct.
tl;dr
django-polymorphic attends the 3 main requirements:
Allow django ORM querying style
Keep db level constraints by having a multi-table inheritance and one table for each sub class
Easy django admin integration
Longer version
Django-polymorphic allows me to query all different event instances from the base class, like:
# assuming the objects where previously created
>>> Event.objects.all()
[<SubEventA object>, <SubEventB object>, <SubEventC object>]
It also has great django admin integration, allowing seamless objects creation and editing.
The models using django-polymorphic would look like:
# A.models.py
from polymorphic import PolymorphicModel
class Event(PolymorphicModel):
commom_field = models.BooleanField()
# no longer abstract
class SubEventA(Event):
email = models.EmailField(unique=True)
class SubEventB(Event):
title = models.TextField()
class SubEventC(Event):
number = models.IntegerField(default=10)
# B.models.py
# it doesnt have to be polymorphic to reference polymorphic models
class OtherModel(models.Model):
event = models.ForeignKey('A.Event')
Besides, I can reference only the base model from another class and I can assign any of the subclasses directly, such as:
>>> sub_event_b = SubEventB.objects.create(title='what a lovely day')
>>> other_model = OtherModel()
>>> other_model.event = sub_event_b
My .2c on this. Not sure about your design in #3. Each SubEvent subclasses Event, and has a one-to-one to Event? Isn't that the same thing?
Your proposal on the Generic Key is exactly what it is designed for.
Another possibility - Polymorphism with Mixins. Use something like Django-polymorphic, so querying returns you the subclass you want. I use this all the time and its super useful. Then make Mixins for attributes that will be reused across many classes. So a simple example, making an email Mixin
class EmailMixin(models.Model):
email = models.EmailField(unique=True)
class Meta:
abstract = True
Then use it
class MySubEvent(EmailMixin, models.Model):
<do stuff>
This way you dont have redundant attributes on subclasses, as you would if they were all in the parent.
Why not a multi-table inheritance?
class Event(models.Model):
commom_field = models.BooleanField()
class SubEventA(Event):
email = models.EmailField(unique=True)
class SubEventB(Event):
title = models.TextField(blank=True)
class SubEventC(Event):
number = models.IntegerField(default=10)

Categories

Resources