I have a many-to-many self-referencing relationship that includes a through model. Products are composed of other Products, including an amount (I.E a screw is made from iron rods). I made the through model, although I can't seem to access the 'amount' field in the through model:
Model code:
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200, unique=True)
produced = models.IntegerField(default=0)
speed = models.IntegerField()
type = models.CharField(max_length=200)
ingredients = models.ManyToManyField('self', through='RecipeComponent', symmetrical=False)
def __str__(self):
return self.name
class RecipeComponent(models.Model):
item = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="item")
ingredient = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="ingredient")
amount = models.IntegerField()
def __str__(self):
return str(self.amount) + " of " + self.ingredient.name
I've tried a number of other queries, but as you can see they seem to fail. I know I'm missing something but I can't put my finger on it.
screw.ingredients.all()
>>> <QuerySet [<Product: iron_rod>]>
screw.ingredients.all()[0].amount
>>> Traceback (most recent call last):
>>> File "<input>", line 1, in <module>
>>> AttributeError: 'Product' object has no attribute 'amount'
screw.ingredients.all()[0]
Is a product, and if you try:
screw.ingredients.all()[0].amount
you're trying to access the amount attribute of Product which does not have. Hence the error:
>>> AttributeError: 'Product' object has no attribute 'amount'.
If you want to get the amount describing some relation you can:
relation_data = RecipeComponent.objects.get(product=screw, ingredient=wood)
relation_data.amount # The amount of wood for screw.
Or you want to know how many different ingredients has screw:
RecipeComponent.objects.filter(product=screw).count()
You can create a method within your Product class that returns the amount of the given ingredient:
class Product(models.Model):
...
...
def get_amount_of_ingredient(self, ingredient):
try:
return RecipeComponent.objects.get(item=self, ingredient=ingredient).amount
except RecipeComponent.DoesNotExist:
return 0
Then call it from screw object:
screw.get_amount_of_ingredient("iron_rod")
Related
I have the following three models that are in a many-to-many relationship:
PokemonTypesTest and BasePokemonTest are linked via an intermediary table WeakResistImmune
class PokemonTypesTest(models.Model):
name = models.CharField(max_length=25, unique=True)
def __str__(self):
return self.name
class BasePokemonTest(models.Model):
name = models.CharField(max_length=50, unique=True)
base_hp = models.IntegerField()
base_atk = models.IntegerField()
base_def = models.IntegerField()
base_spatk = models.IntegerField()
base_spdef = models.IntegerField()
base_speed = models.IntegerField()
type1 = models.ForeignKey(PokemonTypesTest, null=True, on_delete=models.SET_NULL, related_name='pokemontype1')
type2 = models.ForeignKey(PokemonTypesTest, null=True, on_delete=models.SET_NULL, related_name='pokemontype2')
type3 = models.ForeignKey(PokemonTypesTest, null=True, on_delete=models.SET_NULL, related_name='pokemontype3')
ability1 = models.CharField(max_length=25, default='n/a') #these abilities (ability1, ability2 and hidden_ability will need to be moved to a many-to-many table)
ability2 = models.CharField(max_length=25, default='n/a') #where a pokemon id from basepokemon table is linked to a ability id
hidden_ability = models.CharField(max_length=40, default='n/a')
pokemon_id = models.IntegerField(default=0)
weaknessResistanceImmune = models.ManyToManyField(PokemonTypesTest, through='WeakResistImmune')
class WeakResistImmune(models.Model):
#in the typing field, 1 is weakness
pokemon = models.ForeignKey(BasePokemonTest, on_delete=models.CASCADE)
weak_resist_immune = models.CharField(max_length=25)
typing = models.ForeignKey(PokemonTypesTest, on_delete=models.CASCADE)
multiplier = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return str(self.pokemon.name)
I was going through the docs and thought the following would work:
data = BasePokemonTest.objects.prefetch_related('weaknessResistanceImmune')
Data returns the following query set
<QuerySet [<BasePokemonTest: Base for - Bulbasaur>, <BasePokemonTest: Base for - Ivysaur>, <BasePokemonTest: Base for - Venusaur>, <BasePokemonTest: Base for - Mega Venusaur>, <BasePokemonTest: Base for - Charmander>]>
I used the prefetch on the many-to-many relationship field in the BasePokemonTest model.
However, when I try to access data, I get an error-
>>> data[0]
<BasePokemonTest: Base for - Bulbasaur>
>>> data[0].weaknessResistanceImmune
<django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x00000235D117BD30>
>>> data[0].weaknessResistanceImmune.weak_resist_immune
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'ManyRelatedManager' object has no attribute 'weak_resist_immune'
Not sure where I am messing up lol. Any help would be appreciated!
EDIT
heres some more info below. It looks like for whatever reason, the prefetch isn't working as intended.
It's only grabbing one type entry? Instead of all the relationships
>>> for i in data:
... print(i)
...
Base for - Bulbasaur
Base for - Ivysaur
Base for - Venusaur
Base for - Mega Venusaur
Base for - Charmander
>>> for i in data:
... print(i.weakness_resistance_immune.all())
...
<QuerySet [<PokemonTypesTest: Flying>]>
<QuerySet []>
<QuerySet []>
<QuerySet []>
<QuerySet []>
Let's say I have the following simple Django models:
class Club(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField(max_length=50, unique=True)
club = models.ForeignKey(Club, on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return self.name
And I create the following objects:
club1 = Club.objects.create(name="Club1")
student1 = Student.objects.create(name="Student1", club=club1)
print(club1.student_set.all())
# <QuerySet [<Student: Student1>]>
Now I'm going to instantiate a second student object on the club object but NOT YET persist it to the database. Is there any way to get all students associated with club1 BEFORE it has been written to the DB? Looks like using the standard approach just returns the objects stored in the DB:
student2 = Student(name="Student2", club=club1)
print(club1.student_set.all())
# <QuerySet [<Student: Student1>]>
# Goal is to get this:
# <QuerySet [<Student: Student1>, <Student: Student2>]>
The reason I need this is to perform some validation of the staged data state.
If you want to perform validation of the student added, you should use the signal that gets triggered on the save. If you raise an error in the validation, the save does not get performed.
from django.db.models.signals import m2m_changed
m2m_changed.connect(student_set_changed, sender=Club.student_set.through)
def student_set_changed(*args, **kwargs):
# Do you validation
return
More on how to handle Many2Many field changes in Django Docs
You can add related_name attr in order to have easy access to all club's students. And you can validate students' quantity before saving it.
class Student(models.Model):
name = models.CharField(max_length=50, unique=True)
club = models.ForeignKey(
Club,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='students', # add this related name and run migratioin
)
def __str__(self):
return self.name
def validate_club_students(self):
if self.club.students.count() >= 3:
raise ValidationError('The club already has 3 students!')
def save(self, *arg, **kwargs):
self.validate_club_students()
super().save(*arg, **kwargs)
It means that before saving a new student for a certain club you can check how many students does the club have.
i encountered the following problem when i try to migrate list of models one of which contains a ManyToMany field.
class Item(models.Model):
File "C:\Users\helin\Downloads\Django\E-commerce\Farmers\Farmersapp\models.py", line 60, in Item
sluger = farmer.First_Name
AttributeError: 'ManyToManyField' object has no attribute 'First_Name'
Below are the models i created.any help is appreciated.Thank you
class Farmer(models.Model):
id = models.AutoField(default=1,primary_key=True)
First_Name = models.CharField(max_length=15)
Last_Name = models.CharField(max_length=15)
def __str__(self):
return self.First_Name+" "+self.Last_Name
def get_farmer(self):
return self.farmer.First_Name+" " +self.farmer.Last_Name
class Item(models.Model):
id = models.AutoField(default=1,primary_key=True)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=6)
price = models.FloatField()
description = models.TextField(blank=True)
image = models.ImageField()
farmer = models.ManyToManyField(Farmer, through='ItemAmount',related_name='item')
sluger = farmer.First_Name
slug = models.SlugField(default=sluger)
def __str__(self):
return self.category
class ItemAmount(models.Model):
farmer = models.ForeignKey(Farmer, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
First, would suggest to take a look at Python style guide, like lowercase attribute names, etc.
Django auto-creates id primary-key field on every model, unless other field is set as primary-key. So, this part can be safely avoided.
get_farmer method - how is it different from str? Also, these are model instance methods ((self)), so there is no self.farmer field on Farmer object - this will fail.
class Farmer(models.Model):
# id AutoFied is created by default by django, so no need to specify
# id = models.AutoField(default=1,primary_key=True)
first_name = models.CharField(max_length=15)
last_name = models.CharField(max_length=15)
def __str__(self):
return self.first_name + " " + self.last_name
farmer = models.ManyToManyField() - as it is ManyToMany, this means many farmers, so it is better to name field farmers, also same applies to reverse relation - Farmer might have multiple items - related_name=items.
sluger - is it a field? Also, it might have many farmers, so which one to pick?
slug - referencing self fields in default is not good idea, better set default in forms.
You can make slug CharField and set its value in save() method for example.
class Item(models.Model):
# id AutoFied is created by default by django, so no need to specify
# id = models.AutoField(default=1,primary_key=True)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=6)
price = models.FloatField()
description = models.TextField(blank=True)
image = models.ImageField()
farmers = models.ManyToManyField(
Farmer,
through='ItemAmount',related_name='items'
)
slug = models.SlugField()
def __str__(self):
return self.category
You can start with minimum working models and add new fields / methods one by one - it would be easier to debug and you will have base working app.
I am using the following models for my first django site. But I am currently having problems with how to access the wishes of a user.
class Group(models.Model):
title = models.CharField(max_length=100)
users = models.ManyToManyField(User, related_name='group_users')
description = models.TextField()
added = models.DateTimeField()
owner = models.ForeignKey(User)
def __unicode__(self):
return self.title
class Wish(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, related_name='wish_user')
bought = models.IntegerField(default=0)
bought_by = models.ForeignKey(User, related_name='wish_buyer')
added_by = models.ForeignKey(User, related_name='wish_adder')
cost = models.FloatField()
added = models.DateTimeField()
def __unicode__(self):
return self.name
def is_bought(self):
return self.bought % 2 == 1
def is_editable(self):
return self.added >= timezone.now() - datetime.timedelta(hours=1)
When I go to the django shell I get the following:
$ ./manage.py shell
>>> from django.contrib.auth.models import User
>>> from wish.models import Wish, Group
>>> user1 = User.objects.filter(id=1)[0]
>>> user1.group_set.all()
[]
>>> user1.wish_set.all()
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'wish_set'
>>>
Why doesn't the User object get the wish_set like it does get the group_set ?
That's because you renamed them to wish_user, wish_buyer and wish_adder. Whereas for the group you have wish_set implicity from the owner property and the explicit group_users.
The related_name parameter tells Django how to name the reverse relation. If it's not given it will be <field name>_set
I'm sorta having a django / db brainfart.
In words I'm trying to come up with a better way to represent Address objects in my project. I have 5 or 6 different models that have one or more associations to an Address (Billing / Shipping / etc) and I need to allow these addresses to be created/modified/deleted. My first thought was to use the Admin for this since it seems like a natural fit.
However, I can't seem to figure out how to tell the Admin to restrict the visible set of addresses to the specific model in question(Account/Partner/Invoice). I found a really nasty, not viable, and incredibility horrible to maintain way to do it, which I will show below.
How can I do this efficiently (preferably in the Admin)? I am open to going to a m2m relationship which feels much more natural to me, and using a custom view/form, but I wanted to see if I was missing some Admin trick before I went that route. If I go that route I'm thinking I will need to use a GenericRelation so that I don't end up with a ton of lookup tables (one for each different entity).
EDIT: The same address may be used for different models, and for different instances of a particular model, BUT if an address is reused we must track who is using what to maintain independence between models and/or instances. Or in other words in a m2m relationship we can track who is using what with the intermediate table. If we aren't using a lookup table then we need to always copy the Address instance so that both objects have their own copy. (If there is an edit we need to make sure we aren't changing the preexisting relationships of anyone else. In other words edits are actually creates and reassigns in the m2m case.)
Here is an example that should pretty much work as an empty project that sorta shows how I want the address(es) isolated during add/edit/delete, but it also shows how horrible the solution is.
models.py
from django.db import models
class Account(models.Model):
name = models.CharField(max_length=200,blank=True)
#...
def __unicode__(self):
return u"%s - (%s)" %(self.name, self.address_set.all())
class Partner(models.Model):
name = models.CharField(max_length=200,blank=True)
discount = models.DecimalField(max_digits= 3, decimal_places=1, default=0)
#...
def __unicode__(self):
return u"%s - (%s)" %(self.name, self.address_set.all())
class Invoice(models.Model):
invoice_number = models.IntegerField(default=1)
#...
def __unicode__(self):
return u"%s - (%s)" %(self.invoice_number, self.address_set.all())
class Address(models.Model):
street = models.CharField(max_length=200,blank=True)
zip = models.CharField(max_length=10, verbose_name='Zip Code')
account = models.ForeignKey(Account, blank=True, null=True)
partner = models.ForeignKey(Partner, blank=True, null=True)
invoice = models.ForeignKey(Invoice, blank=True, null=True)
type = models.CharField(max_length=25, choices=(('B','Billing'),('S','Shipping')))
class Meta:
unique_together = (('type', 'account' ),
('type', 'partner' ),
('type', 'invoice' ),)
def __unicode__(self):
return "(%s) - %s %s" %(self.get_type_display(), self.street, self.zip)
admin.py
from django.contrib import admin
from relationships.rels.models import Partner, Account, Address, Invoice
class AcctAddrInline(admin.TabularInline):
model = Address
extra = 1
max_num =3
exclude = ('partner', 'invoice')
class PartAddrInline(admin.TabularInline):
model = Address
extra = 1
max_num =3
exclude = ('account', 'invoice')
class InvAddrInline(admin.TabularInline):
model = Address
extra = 1
max_num =2
exclude = ('account', 'partner')
class AccountAdmin(admin.ModelAdmin):
inlines = [AcctAddrInline,]
class PartnerAdmin(admin.ModelAdmin):
inlines = [PartAddrInline,]
class InvoiceAdmin(admin.ModelAdmin):
inlines = [InvAddrInline,]
admin.site.register(Invoice, InvoiceAdmin)
admin.site.register(Partner, PartnerAdmin)
admin.site.register(Account, AccountAdmin)
admin.site.register(Address)
EDIT: The same address may be used for different models, and for different instances of a particular model, BUT if an address is reused we must track who is using what to maintain independence between models and/or instances
It looks like you want to use COW pattern for addresses, but I don't think it plays nice with the whole idea of database integrity.
If you just want to separate account addresses from invoice addresses I'd suggest to use Multi-table model inheritance. This way you will have several sets of addresses while being able to browse all addresses at once.
Here's an example.
models.py
from django.db import models
class Account(models.Model):
name = models.CharField(max_length=200, blank=True)
def __unicode__(self):
return u"%s - (%s)" % (self.name, self.address_set.all())
class Partner(models.Model):
name = models.CharField(max_length=200, blank=True)
discount = models.DecimalField(max_digits= 3, decimal_places=1, default=0)
def __unicode__(self):
return u"%s - (%s)" % (self.name, self.address_set.all())
class Invoice(models.Model):
invoice_number = models.IntegerField(default=1)
def __unicode__(self):
return u"%s - (%s)" % (self.invoice_number, self.address_set.all())
class Address(models.Model):
street = models.CharField(max_length=200, blank=True)
zip = models.CharField(max_length=10, verbose_name='Zip Code')
def __unicode__(self):
return "%s %s" % (self.street, self.zip)
class AccountAddress(Address):
account = models.ForeignKey(Account, related_name='address_set')
class InvoiceAddress(Address):
invoice = models.ForeignKey(Invoice, related_name='address_set')
class PartnerAddress(Address):
partner = models.ForeignKey(Partner, related_name='address_set')
admin.py
from django.contrib import admin
# Wildcard import used for brevity
from relationships.rels.models *
class AccountAddressInline(admin.TabularInline):
model = AccountAddress
extra = 1
max_num = 3
class PartnerAddressInline(admin.TabularInline):
model = PartnerAddress
extra = 1
max_num = 3
class InvoiceAddressInline(admin.TabularInline):
model = InvoiceAddress
extra = 1
max_num = 3
class AccountAdmin(admin.ModelAdmin):
inlines = [AccountAddressInline,]
class PartnerAdmin(admin.ModelAdmin):
inlines = [PartnerAddressInline,]
class InvoiceAdmin(admin.ModelAdmin):
inlines = [InvoiceAddressInline,]
admin.site.register(Account, AccountAdmin)
admin.site.register(Partner, PartnerAdmin)
admin.site.register(Invoice, InvoiceAdmin)
admin.site.register(AccountAddress)
admin.site.register(InvoiceAddress)
admin.site.register(PartnerAddress)
# Uncomment if you want to browse all addresses available at once
# admin.site.register(PartnerAddress)
Note the related_name='address_set' hack. I don't know why, but it is the only way inline editing works when using foreign key from an inherited model. It seems that it's a bug in Django similar to (but rather with a reversed use-case) #11120 and #11121.
I, personally, would put foreign keys to your Address model on Account, Partner and Invoice instead of having Address aware of what it is the address to. That, MAY, solve your problem.