class Basket:
name = models.CharField(max_length=50, blank=True, null=True)
class Apple:
name = models.CharField(max_length=50, blank=True, null=True)
basket = models.ForeignKey(Basket, on_delete=models.PROTECT)
...
myapple = new Apple(name="my")
myapple.save()
...
auto_created_basket = myapple.basket
myapple.basket = existing_basket
auto_created_basket.delete()
I try to swap out the auto_created_basket to another one, but I get an error when I try to delete it.
"Cannot delete some instances of model 'Basket' because they are referenced through a protected foreign key: 'Apple.basket'", [<Apple: My apple>])
In your Apple model the basket field is a foreing key
basket = models.ForeignKey(Basket, on_delete=models.PROTECT)
whose on_delete attribute value specifically states to protect the apples by preventing the deletion of the basket.
As the official docs say
When an object referenced by a ForeignKey is deleted, Django by
default emulates the behavior of the SQL constraint ON DELETE CASCADE
and also deletes the object containing the ForeignKey.
and
the PROTECT parameter prevents deletion of the referenced object by raising
ProtectedError
So, the easiest step should be to remove the on_delete parameter and go with the default behaviour
basket = models.ForeignKey(Basket)
However, please have a look at all the possible parameters to a ForeignKey model field and choose the combination that suits the requirements of your application/scenario.
UPDATE:
Recent Django versions require on_delete. Just don't remove it and add the parameter you want (like on_delete=models.CASCADE).
I hate to answer my question, but my example was too over simplified. The answers are very good points.
In the real product there are post_save signals involved, responsible for the creation of the auto_created_basket for example.
The problem is that when I say myapple.basket = existing_basket Django's layer is fine, but the DB is still holding the reference to older relation. The solution in my case was to move the auto_created_basket.delete() after I save myapple once more.
You can try moving apples from auto_created_basket into existing_basket before deleting the basket first:
>>> auto_created_basket.apple_set.update(basket=existing_basket)
>>> auto_created_basket.delete()
or
>>> myapple.basket = existing_basket
>>> myapple.save()
>>> auto_created_basket.delete()
Alternatively, if you want to collect apples that are from deleted baskets in a single basket, you can assign a function to on_delete property as follows:
def get_sentinel_basket():
basket, created = Basket.objects.get_or_create(name='DELETED')
return basket
--
basket = models.ForeignKey(Basket, on_delete=models.SET(get_sentinel_basket))
so when a basket is deleted, the .basket attribute of apples in that basket will automatically be set to Basket(name='DELETED').
Related
I have a ManyToMany relationship that indicates a Doctor can have many specialties, but only one of them is the PRIMARY one.
I've designed a custom M2M class as follows:
class Doctor(models.Model):
account = models.ForeignKey(Account, on_delete=models.CASCADE)
specialty = models.ManyToManyField(Specialty, through='DoctorSpecialty')
.....
class Specialty(models.Model):
title = models.CharField(max_length=45)
.....
class DoctorSpecialty(models.Model):
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
specialty = models.ForeignKey(Specialty, on_delete=models.CASCADE)
default = models.BooleanField(default=True)
The doctor can have many specialties, but only one of them can be the default one. He or she can have many specialties with the default field set as False, but cannot have more than one with the default field set as True
I wanted to do something like this:
class Meta:
constraints = [
models.UniqueConstraint(fields=['doctor', 'specialty', 'default'], name='unique specialty')
]
But this will mean that the doctor can have only one specialty as a default one, and only one other as a non default one.
How can we achieve this with the minimum of code?
PS: I could leave it without constraints and try to validate adding new entries by checking if another default specialty exists, but this will add a lot of overhead and exception raising.
I think there is no way we can achieve this with built-in functions. So I came up with this solution (since no one else answered):
I created another ForeignKey for the Primary Specialty, and ditched the DoctorSpecialty custom M2M class and left the M2M relationship with Specialty. One doctor can have only one primary specialty, and can also choose additional specialties as secondary. Later on in the views, I can put in place an algorithm to remove the primary specialty from the list of specialties when entering additional ones in case there is an existing primary specialty.
I have these models in my Django app:
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Animal(models.Model):
name = models.CharField(max_length=100, unique=True)
class AnimalList(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
list = models.ManyToManyField(Amimal)
I want to add the same pokemon to the same list twice:
>>> animal = Animal.objects.create(name='milla')
>>> user = User.objects.create(username='user')
>>> list = AnimalList.objects.create(user=user)
>>> list.list.add(animal)
>>> list.list.add(animal)
>>> animal.save()
The animal is added only once, though:
>>> list.list.all()
<QuerySet [<Animal: Animal object (3)>]>
I expected that, the documentation is explicit that
Adding a second time is OK, it will not duplicate the relation:
Yet, I do want to repeat the animals.
How could I do that? Is it possible by using ManyToManyField?
I could not repeat these relationships because Django applies a UniqueConstrant to many-to-many relationships. My solution was to add another table that referenced the animals:
class AnimalListItem(models.Model):
animal = models.ForeignKey(Animal, on_delete=models.CASCADE)
class AnimalList(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
list_items1 = models.ManyToManyField(AnimalListItem)
After that, every time I wanted to add an animal to a list, I had to create a list item first, pointing to the animal, then add this list item to the list column.
There are other possible solutions as well. For example, the through argument from ManyToManyField disables any default constraints that would be added to the relationship table. Or you could set db_constraint to False without through.
However, those were not solutions to me because, as the documentation states:
If the custom through table defined by the intermediate model does not enforce uniqueness on the (model1, model2) pair, allowing multiple values, the remove() call will remove all intermediate model instances
I needed to remove only one instance at a time, so removing all of them would not be feasible.
How can I store history of ManyToManyField using django-simple-history. I used HistoricalRecords with attribute m2m_filds but it is throwing error: unexpected keyword argument 'm2m_fields'
I'm macro1 on GitHub, and I guess de facto maintainer of django-simple-history.
From your question it seems that you're just asking about general ManyToManyField support compared with other fields. The short answer is that we do not currently support it.
ManyToManyFields actually create an in-between model that represents the relationship between the two models you're working with.
If you want tracking on that relationship I would suggest making a 'through' model representing the relationship and passing that into the ManyToManyField constructor. You could then register that through model to have its history tracked. If you get errors like "unexpected keyword argument 'm2m_fields'" with that set up please open an issue in our tracker.
Even though django-simple-history does not allow to have history tables for many to many relations there is actually a way to achieve this.
What you can do is that you manually create the many to many table and instead of using djangos add and remove you simply create and delete the relations. If you look at it with an example we would have:
class Class(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class Student(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
classes = models.ManyToManyField(Class)
history = HistoricalRecords()
you can manually create the many to many table with:
class Class(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class Student(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class StudentClasses(models.Model):
student = models.ForeignKey(Student)
class = models.ForeignKey(Class)
history = HistoricalRecords()
if you now use:
StudentClasses.objects.create(student=student, class=class) instead of student.classes.add(class) and delete() instead of student.classes.remove(class) you will have everything tracked in a history table and the same many to many table.
As the author of django-simple-history says this isn't possible to detect change in only specific fields because
As you already know simple-history doesn't look at the values being
saved at all. It blindly saves a new historical version on every save
or delete signal.
He also says it may be possible Field Tracker do this job.
Before I start: My understanding of Django is at a beginner Level and i could not find adequate help through google.
I'll start with an example:
class Player(models.Model):
...
class Tournament(models.Model):
...
first_place = models.ForeignKey(Player)
second_place = models.ForeignKey(...)
third_place = models.ForeignKey(...)
My problem is: there are multiple people in first place, second place and so on. How can I realize the model in a way which lets me add my own number of Players every time?
I already tried ManyToMany instead of ForeignKey but then I get an Error in the admin menu when i try to save a Tournament Object stating that there has to be an ID present for the Object even when I do not select any Players to be added.
Don't know if I understand the question correctly, but if you want to make ForeignKeys optional and also want to add multiple Players, you could use ManyToManyField and set null and blank, both True:
class Tournament(...):
...
first_place = models.ManyToManyField(Player, blank=True, null=True)
...
This would solve your problem you can now freely add each and any place to each player. The .count() function in your view can get the number of objects for the selected players. No need for a manytomanyfield when you can just assign a place to an object by the user with every user now being able to have unlimited places in each category, if I understand what your trying to do here. Comment if you need more help.
Class FirstPlace(models.Model):
first = models.ForeignKey(Player)
Class SecondPlace(models.Model):
second = models.ForeignKey(Player)
Class ThirdPlace(models.Model):
third = models.ForeignKey(Player)
I've a Django model
class Person(models.Model):
name = models.CharField(max_length=50)
team = models.ForeignKey(Team)
And a team model
class Team(models.Model):
name = models.CharField(max_length=50)
Then, I would like to add a 'coach' property which is a one to one relationship to person. If I am not wrong, I have two ways of doing it.
The first approach would be adding the field to Team:
class Team(models.Model):
name = models.CharField(max_length=50)
coach = models.OneToOneField(Person, related_name='master')
The second one would be creating a new model:
class TeamCoach(models.Model):
team = models.OneToOneField(Team)
coach = models.OneToOneField(Person)
Is this right ? is there a big difference for practical purposes ? which are the pro and cons of each approach ?
I will say NEITHER, as every Person has a Team and if every Team has a Coach, it's rather redundant circulation and somewhat unnecessary.
Better to add a field in Person called type directly is more clean and direct, something like:
class Person(models.Model):
# use _ if you care about i18n
TYPES = ('member', 'member',
'coach', 'coach',)
name = models.CharField(max_length=50)
team = models.ForeignKey(Team)
type = models.CharField(max_length=20, choices=TYPES)
Although I would seriously consider refactoring Person to be more generic and get Team to have a ManyToMany to Person... in that case, you can re-use Person in other areas, like Cheerleaders.
class Person(models.Model):
# use _ if you care about i18n
TYPES = ('member', 'member',
'coach', 'coach',)
name = models.CharField(max_length=50)
type = models.CharField(max_length=20, choices=TYPES)
class Team(models.Model):
name = models.CharField(max_length=50)
member = models.ManyToManyField(Person, related_name='master')
Make your models more generic and DRY, they should be easily manageable and not tightly coupled to certain fields (unless absolutely necessary), then the models are more future proof and will not fall under migration nightmare that easily.
Hope this helps.
I can't agree so easy with #Anzel, and since the name of the question is
What are the benefits of having two models instead of one?
I'll try to give my two cents. But before i start i want to place some quotes from the docs.
It doesn’t matter which model has the ManyToManyField, but you should
only put it in one of the models – not both.
Generally, ManyToManyField instances should go in the object that’s
going to be edited on a form. In the above example, toppings is in
Pizza (rather than Topping having a pizzas ManyToManyField ) because
it’s more natural to think about a pizza having toppings than a
topping being on multiple pizzas. The way it’s set up above, the Pizza
form would let users select the toppings.
Basically that's the first thing you should have in mind when creating a M2M relation (your TeamCoach model is that, but more on that in a second) which one is the object holding the relation. What would be more suitable for your problem - choosing a coach for a team when you create it, or choosing a team for a person when you create it? IF you ask me i would prefer the second variant and keep the teams inside of the Person class.
Now lets go to the next section of the docs
Extra fields on many-to-many relationships
When you’re only dealing with simple many-to-many relationships such
as mixing and matching pizzas and toppings, a standard ManyToManyField
is all you need. However, sometimes you may need to associate data
with the relationship between two models.
For example, consider the case of an application tracking the musical
groups which musicians belong to. There is a many-to-many relationship
between a person and the groups of which they are a member, so you
could use a ManyToManyField to represent this relationship. However,
there is a lot of detail about the membership that you might want to
collect, such as the date at which the person joined the group.
For these situations, Django allows you to specify the model that will
be used to govern the many-to-many relationship. You can then put
extra fields on the intermediate model. The intermediate model is
associated with the ManyToManyField using the through argument to
point to the model that will act as an intermediary.
That's actually the answer of your question, having an intermediate model give you the ability to store additional data about the collection. Consider the situation where a coach moves to another team next season, if you just update the M2M relation, you will loose the track of his past teams where he was coaching. Or you will never be able to answer the question who was the coach of that team at year XXX. So if you need more data, go with intermediate model. This is also were #Anzel going wrong, the type field is an additional data of that intermediate model, it's place must be inside it.
Now here is how i would probably create the relations:
class Person(models.Model):
name = models.CharField(max_length=50)
teams = models.ManyToManyField('Team', through='TeamRole')
class Team(models.Model):
name = models.CharField(max_length=50)
class TeamRole(models.Model):
COACH = 1
PLAYER = 2
CHEERLEADER = 3
ROLES = (
(COACH, 'Coach'),
(PLAYER, 'Player'),
(CHEERLEADER, 'Cheerleader'),
)
team = models.ForeignKey(Team)
person = models.ForeignKey(Person)
role = models.IntegerField(choices=ROLES)
date_joined = models.DateField()
date_left = models.DateField(blank=True, null=True, default=None)
How will I query this? Well, I can use the role to get what type of persons I'm looking for, and I can also use the date_left field to get the current persons participating in that team right now. Here are a few example methods:
class Person(models.Model):
#...
def get_current_team(self):
return self.teams.filter(teamrole__date_left__isnull=True).get()
class Team(models.Model):
#...
def _get_persons_by_role(self, role, only_active):
persons = self.person_set.filter(teamrole__role=role)
if only_active:
return persons.filter(teamrole__date_left__isnull=True)
return persons
def get_coaches(self, only_active=True):
return self._get_persons_by_role(TeamRole.COACH, only_active)
def get_players(self, only_active=True):
return self._get_persons_by_role(TeamRole.PLAYER, only_active)