I've the following models:
class A(models.Model):
title = models.CharField(max_length=30)
class B(models.Model):
title = models.CharField(max_length=255)
a = models.ManyToManyField(A)
I now want a model C which has a field that has multiple unique combinations of A and B like in ManyToMany field. I found that for mapping ForeignKey to two models GenericForeignKey can be used. I've tried GenericForeignKey like below:
class C(models.Model):
title = models.CharField()
property1 = models.CharField()
a = models.ManyToManyField(A)
b = models.ManyToManyField(B)
a_b = GenericForeignKey(store, category)
But, this doesn't seem to work. Can someone point me in the right direction? I'm new to Django as well as SQL.
Edit: Clarified the requirement of combinations.
Edit2: Current working model:
I've created below models as a working solution:
class A(models.Model):
title = models.CharField(max_length=30)
class B(models.Model):
title = models.CharField(max_length=255)
a = models.ManyToManyField(A)
class C(models.Model):
title = models.CharField()
property1 = models.CharField()
class A_B_C(models.Model):
a = models.ForeignKey(A)
b = models.ForeignKey(B)
c = models.ForeignKey(C)
class Meta:
unique_together = ('a', 'b', 'c')
It is this model that I was avoiding to create explicitly and maintain within relations. I would like to know if this solution would be efficient or is there another Django way to do things?
When using ManyToManyField Django creates intermediate table for you implicitly. In your example you'll get additional table with columns (id, a_id, b_id). But you can define it explicitly and set a through option on a field a on B model, and then define unique_together for this intermediate model:
class A(models.Model):
title = models.CharField(max_length=30)
class B(models.Model):
title = models.CharField(max_length=255)
a = models.ManyToManyField(A, through='C')
class C(models.Model):
a = models.ForeignKey(A, on_delete=models.CASCADE)
b = models.ForeignKey(B, on_delete=models.CASCADE)
class Meta:
unique_together = ("a", "b")
Note: If I remember correctly, you can't set unique_together on ManyToManyFiled directly.
This isn't a job for GenericForeignKey. Instead you need to define the unique_together Meta attribute:
class C(models.Model):
...
class Meta
unique_together = ('a', 'b')
For future reference:
I've created below models as a working solution:
class A(models.Model):
title = models.CharField(max_length=30)
class B(models.Model):
title = models.CharField(max_length=255)
a = models.ManyToManyField(A)
class C(models.Model):
title = models.CharField()
property1 = models.CharField()
class A_B_C(models.Model):
a = models.ForeignKey(A)
b = models.ForeignKey(B)
c = models.ForeignKey(C)
class Meta:
unique_together = ('a', 'b', 'c')
Related
I have 4 models:
class User(models.Model):
name = models.CharField(max_length=255)
class A(models.Model):
user= models.ForeignKey("User", related_name="u_a", on_delete=models.CASCADE)
title = models.CharField(max_length=255)
class B(A):
user= models.ForeignKey("User", related_name="u_b", on_delete=models.CASCADE)
#isn't the code repeated???
b_field = CharField(max_length=255)
class C(A):
user= models.ForeignKey("User", related_name="u_c", on_delete=models.CASCADE)
#isn't the code repeated???
c_field = CharField(max_length=255)
Here, A has a ForeignKey relationsip with User and a reverse relationship as u_a. But B and C are children of A.
So It appears to me as if Do not repeat your code is violated. How to overcome this?
To work around this problem, In your model class A(models.Model) The part of the value should contain '%(app_label)s' and/or '%(class)s'. see the doc
'%(class)s' is replaced by the lower-cased name of the child class that the field is used in.
'%(app_label)s' is replaced by the lower-cased name of the app the child class is contained within. Each installed application name must be unique and the model class names within each app must also be unique, therefore the resulting name will end up being different.
class A(models.Model):
user= models.ForeignKey("User", related_name="%(class)s_set",
on_delete=models.CASCADE)
#user= models.ForeignKey("User", related_name="%(app_label)s_%(class)s_set",
#on_delete=models.CASCADE)
title = models.CharField(max_length=255)
class B(A):
b_field = CharField(max_length=255)
class C(A):
c_field = CharField(max_length=255)
class ModelA(models.Model):
name = models.CharField(max_length=100)
code = models.CharField(max_length=100, default='')
class ModelB(models.Model):
name = models.CharField(max_length=100)
code = models.CharField(max_length=100, default='')
class ModelC(models.Model):
name = models.CharField(max_length=100)
modelA = models.ForeignKey(ModelA, on_delete=models.CASCADE)
modelB = models.ForeignKey(ModelB, on_delete=models.CASCADE)
These are the models.
class ModelASerializer(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = "__all__"
class ModelBSerializer(serializers.ModelSerializer):
class Meta:
model = ModelB
fields = "__all__"
class ModelC(serializers.ModelSerializer):
modelA = ModelASerializer()
modelB = ModelBSerializer()
class Meta:
model = ModelC
fields = "__all__"
These are my serializers.
Right now I have no problem with a GET request. What I want to achieve is that when I do a post request for ModelC and if ModelA and ModelB do not already exist then create them. Right now I am able to do post with existing modelA and modelB by removing the nested serializer.
Thanks for everyone's help.
I've the models defined as follows:
class A(models.Model):
name_a = models.CharField()
class B(models.Model):
a = models.ForeignKey(A, related_name='aa')
name_b = models.CharField()
class C(models.Model):
b = models.ForeignKey(B, related_name='bb')
name_c = models.CharField()
I'm making a RESTful webservice for model A and I've a queryset defined as
queryset = A.objects.prefetch_related('aa').all()
But I want data from model C as well. But I'm not sure how can I do that?
You can traverse relationships with the double underscore:
queryset = A.objects.prefetch_related('aa','aa__bb').all()
It becomes clearer without using related names:
class A(models.Model):
name = models.CharField()
class B(models.Model):
a = models.ForeignKey(A)
name = models.CharField()
class C(models.Model):
b = models.ForeignKey(B)
name = models.CharField()
A.prefetch_related('b_set','b_set__c_set').all()
Example from the documentation:
>>> Restaurant.objects.prefetch_related('pizzas__toppings')
Where Pizza has a Many-to-Many relationship with Toppings.
Here are my models
# Models
class Category(models.Model):
parent = models.ForeignKey('Category', null=True, blank=True, related_name="children")
name = models.CharField(max_length=64)
alternate_naming = models.ManyToManyField('businesses.Office', through='CategoryOfficeNaming', blank=True)
class CategoryOfficeNaming(models.Model):
category = models.ForeignKey('Category')
office = models.ForeignKey('businesses.Office')
name = models.CharField(max_length=64)
And here are my serializers
# Serializers
class CategoryOfficeNamingSerializer(serializers.ModelSerializer):
class Meta:
model = CategoryOfficeNaming
fields = (
'office',
'name',
)
class CategorySerializer(serializers.ModelSerializer):
# We need special recursive serialization here for Category (parent) -> Category (child) relationship
children = serializers.ListSerializer(read_only=True, child=RecursiveField())
alternate_naming = CategoryOfficeNamingSerializer(many=True)
class Meta:
model = Category
fields = (
'children',
'name',
'alternate_naming',
)
I get an error when trying serialize a Category:
AttributeError at /api/categories/
'Office' object has no attribute 'category'
It seems like the Serializer (alternate_naming) points to an Office instance instead of using the through model (CategoryOfficeNaming) -- why is that? I'm probably doing something silly!
A, ha!
It turns out I was misunderstanding when to use through tables a bit. Instead of using a through table, I ended up with this structure and I got something that works for this situation:
Models:
# Models
class Category(models.Model):
parent = models.ForeignKey('Category', null=True, blank=True, related_name="children")
name = models.CharField(max_length=64)
class CategoryOfficeNaming(models.Model):
category = models.ForeignKey('Category', related_name="alternate_namings")
office = models.ForeignKey('businesses.Office')
name = models.CharField(max_length=64)
Serializers:
# Serializers
class CategoryOfficeNamingSerializer(serializers.ModelSerializer):
class Meta:
model = CategoryOfficeNaming
fields = (
'office',
'name',
)
class CategorySerializer(serializers.ModelSerializer):
# We need special recursive serialization here for Category (parent) -> Category (child) relationship
children = serializers.ListSerializer(read_only=True, child=RecursiveField())
alternate_namings = CategoryOfficeNamingSerializer(many=True)
class Meta:
model = Category
fields = (
'children',
'name',
'alternate_namings',
)
I have 2 models as follows. Now I need to inline Model A on Model B's page.
models.py
class A(models.Model):
name = models.CharField(max_length=50)
class B(models.Model):
name = models.CharField(max_length=50)
a = models.ForeignKey(A)
admin.py
class A_Inline(admin.TabularInline):
model = A
class B_Admin(admin.ModelAdmin):
inlines = [A_Inline]
is that possible?? If yes please let me know..
No, as A needs to have a ForeignKey to B to be used as an Inline. Otherwise how would the relationship be recorded once you save the inline A?
You cannot do it as told by timmy O'Mahony. But you can make B inline in A if you want. Or maybe you can manipulate how django display it in
def unicode(self):
models.py
class A(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class B(models.Model):
name = models.CharField(max_length=50)
a = models.ForeignKey(A)
admin.py
class B_Inline(admin.TabularInline):
model = B
class A_Admin(admin.ModelAdmin):
inlines = [
B_Inline,
]
admin.site.register(A, A_Admin)
admin.site.register(B)
Or maybe you want to use many-to-many relationship?
models.py
class C(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class D(models.Model):
name = models.CharField(max_length=50)
cs = models.ManyToManyField(C)
admin.py
class C_Inline(admin.TabularInline):
model = D.cs.through
class D_Admin(admin.ModelAdmin):
exclude = ("cs",)
inlines = [
C_Inline,
]
admin.site.register(C)
admin.site.register(D, D_Admin)
Of course you can do this. Every relationship be it 1-1, m2m or FK will have a reverse accessor. (more on the FK reverse access here)
class A_Inline(admin.TabularInline):
model = A.b_set.through
class B_Admin(admin.ModelAdmin):
inlines = [A_Inline]