I have an existing applicaton where I have to change a regular m2m relationship to a through relation.
I need the ability to add extra parameters to the relation.
My problem is that I need to do it in two places.
This is the standard through example.
class Person(models.Model):
name = models.CharField(max_length=255)
class Group(models.Model):
name = models.CharField(max_length=255)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
I have another model that needs a m2m relation to Membership. My suggestion is the following.
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group, null=True, blank=True)
another_group = models.ForeignKey(AnotherGroup, null=True, blank=True)
class AnotherGroup(models.Model):
name = models.CharField(max_length=255)
members = models.ManyToManyField(Person, through='Membership')
It bugs me that I have to have one field that is null for every instance, or is this my only option?
Another option is using content type in through table.
https://docs.djangoproject.com/en/1.9/ref/contrib/contenttypes/#generic-relations
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Membership(models.Model):
person = models.ForeignKey(Person)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
Related
I have to identical models:
class AnimalGroup(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField(max_length=1000, blank=True)
images = models.ImageField(upload_to="photos/groups")
class AnimalSubGroup(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField(max_length=1000, blank=True)
images = models.ImageField(upload_to="photos/groups")
and they have a on-to-many relationship. So AnimalGroup can have multiple AnimalSubGroups
But as you see they have identical fields.
Question: how to make one model of this?
So that in Admin I can create animalgroups, like:
mammals
fish
And then I can create the subgroups. Like
bigCat and then I select mammals.
Have them inherit an abstract base class and give the subgroup an additional foreign key:
class AbstractAnimalGroup(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField(max_length=1000, blank=True)
images = models.ImageField(upload_to="photos/groups")
class Meta:
abstract = True
class AnimalGroup(AbstractAnimalGroup):
pass
class AnimalSubGroup(AbstractAnimalGroup):
parent = models.ForeignKey(AnimalGroup, on_delete=models.CASCADE)
If you want subgroups to be actual AnimalGroups, you can use multi-table inheritence:
class AnimalGroup(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField(max_length=1000, blank=True)
images = models.ImageField(upload_to="photos/groups")
class AnimalSubGroup(AnimalGroup):
parent = models.ForeignKey(AnimalGroup, on_delete=models.CASCADE)
Now, all AnimalSubGroups will be also contained in AnimalGroup.objects.all().
You can use an abstract base model:
class NameDescImageModel(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField(max_length=1000, blank=True)
images = models.ImageField(upload_to='photos/groups')
class Meta:
abstract = True
class AnimalGroup(NameDescImageModel):
pass
class AnimalSubGroup(NameDescImageModel):
group = models.ForeignKey(AnimalGroup, on_delete=models.CASCADE)
I got this code, but I can't find a way to create a view that retrieve the allergies a patient has.
class Patient(models.Model):
user = models.OneToOneField(User, related_name='patient', on_delete=models.CASCADE)
id_type = models.CharField(max_length=300)
id_number = models.CharField(max_length=300)
creation_date = models.DateField(default=datetime.date.today)
class Allergie(models.Model):
name = models.CharField(max_length=300, default="X")
class PatientAllergies(models.Model):
patient = models.ForeignKey(Patient, related_name="patient_allergies", on_delete=models.CASCADE)
allergie = models.ForeignKey(Allergie, on_delete=models.CASCADE, null=True)
professional_contract = models.ForeignKey(ProfessionalContract, null=True ,on_delete=models.CASCADE)
You can span a ManyToManyField relation over your PatientAllergies model that acts as a junction table:
class Patient(models.Model):
user = models.OneToOneField(User, related_name='patient', on_delete=models.CASCADE)
id_type = models.CharField(max_length=300)
id_number = models.CharField(max_length=300)
creation_date = models.DateField(auto_now_add=True)
allergies = models.ManyToManyField(
'Allergie',
through='PatientAllergies'
)
# …
You can then for a Patient object p with:
p.allergies.all()
An alternative is to filter the Allergie objects with:
Allergie.objects.filter(patientallergies__patient=p)
or with the ManyToManyField:
Allergie.objects.filter(patient=p)
I am following the example in the documentation: https://docs.djangoproject.com/en/3.2/topics/db/models/#extra-fields-on-many-to-many-relationships
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
In this case, given a Person object, how can I access all the groups that Person is in?
You access these with:
Group.objects.filter(members=my_person_object)
at the moment I try to get recipes from my API. I have a Database with two tables one is with recipes and their ids but without the ingredients, the other table contains the ingredients and also the recipe id. Now I cant find a way that the API "combines" those. Maybe its because I added in my ingredient model to the recipe id the related name, but I had to do this because otherwise, this error occurred:
ERRORS:
recipes.Ingredients.recipeid: (fields.E303) Reverse query name for 'Ingredients.recipeid' clashes with field name 'Recipe.ingredients'.
HINT: Rename field 'Recipe.ingredients', or add/change a related_name argument to the definition for field 'Ingredients.recipeid'.
Models
from django.db import models
class Ingredients(models.Model):
ingredientid = models.AutoField(db_column='IngredientID', primary_key=True, blank=True)
recipeid = models.ForeignKey('Recipe', models.DO_NOTHING, db_column='recipeid', blank=True, null=True, related_name='+')
amount = models.CharField(blank=True, null=True, max_length=100)
unit = models.CharField(blank=True, null=True, max_length=100)
unit2 = models.CharField(blank=True, null=True, max_length=100)
ingredient = models.CharField(db_column='Ingredient', blank=True, null=True, max_length=255)
class Meta:
managed = False
db_table = 'Ingredients'
class Recipe(models.Model):
recipeid = models.AutoField(db_column='RecipeID', primary_key=True, blank=True) # Field name made lowercase.
title = models.CharField(db_column='Title', blank=True, null=True, max_length=255) # Field name made lowercase.
preperation = models.TextField(db_column='Preperation', blank=True, null=True) # Field name made lowercase.
images = models.CharField(db_column='Images', blank=True, null=True, max_length=255) # Field name made lowercase.
#ingredients = models.ManyToManyField(Ingredients)
ingredients = models.ManyToManyField(Ingredients, related_name='recipes')
class Meta:
managed = True
db_table = 'Recipes'
When there is no issue it has to be in the serializer or in the view.
Serializer
class IngredientsSerializer(serializers.ModelSerializer):
# ingredients = serializers.CharField(source='ingredients__ingredients')
class Meta:
model = Ingredients
fields = ['ingredient','recipeid']
class FullRecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientsSerializer(many=True)
class Meta:
model = Recipe
fields = ['title','ingredients']
View
class FullRecipesView(generics.ListCreateAPIView):
serializer_class = FullRecipeSerializer
permission_classes = [
permissions.AllowAny
]
queryset = Recipe.objects.all()
This is at the moment my output
But I want e.g. the recipe with id 0 and all the ingredients which have also recipe id 0.
I really hope that you can help me. Thank you so much!
Rename ingredients to some other name in FullRecipeSerializer. It conflicts with ingredients in Recipe model. This should solve your issue. For example
class FullRecipeSerializer(serializers.ModelSerializer):
ingredients_recipe = IngredientsSerializer(many=True, source= 'ingredientid')
class Meta:
model = Recipe
fields = ['title','ingredients_recipe']
I have 2 tables. User and Group. 1:Many relationship. Each user can only belong to a single group.
here's the model.py.
class Group(models.Model):
group_name = models.CharField(max_length=150, blank=True, null=True)
group_description = models.TextField(blank=True, null=True)
group_creator = models.ForeignKey(User, models.DO_NOTHING)
class User(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
...
group = models.ForeignKey(Group, models.DO_NOTHING)
The issue I have is that they are both referencing each other which is acceptable in MySQL and Oracle, but, I get an error when migrating:
group_creator = models.ForeignKey(User, models.DO_NOTHING)
NameError: name 'User' is not defined
Now when I reverse the order (so, User first than Group), I get
group = models.ForeignKey(Group, models.DO_NOTHING, blank=True, null=True)
NameError: name 'Group' is not defined
This is getting quite frustrating. I have a few work around (make it a many:many and keep creator on Group class), but before I start destroying my datamodel and move data move all the data around, I wonder if anyone has this issue before. How did you solve this? Do you really have to change your datamodel?
as Pourfar mentioned in a comment, you may avoid the NameError via the quoting the model object as string. also it is safe to set related_name for accessing this relation.
class Group(models.Model):
...
group_creator = models.ForeignKey('User', related_name='creator_set')
and then, with your constraint,
Each user can only belong to a single group.
in that case, OneToOneField is more appropriate.
class User(models.Model):
...
group = models.OneToOneField(Group)
then you can access the relations as follows:
# USER is a User object
GROUP_BELONGED = USER.group # access to 1-1 relation
GROUP_CREATED = USER.creator_set.all() # reverse access to foreignkey relation
# now GROUP_BELONGED is a Group object
CREATOR = GROUP_BELONGED.group_creator # access to foreignkey relation
Add related_name to your ForeignKey fields:
class Group(models.Model):
group_name = models.CharField(max_length=150, blank=True, null=True)
group_description = models.TextField(blank=True, null=True)
group_creator = models.ForeignKey('User',related_name='myUser')
class User(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
group = models.ForeignKey('Group', related_name='MyGroup')