Django many-to-many relationship with extra fields - django

I am building a simple interface to a biological database using the django-admin to populate the db. I want tot to use a many-to-many relationship for a questionnaire to fish species (one questionnaire can have more than one species and one species can be present in more than one questionnaire). The two models in question:
class Species(models.Model):
fish_spp_name = models.CharField(max_length=255, unique=True)
class Questionaire(models.Model):
# ...
fish_caught = models.ManyToManyField(Species)
now, I want to my data to contain a number of each species caught, per questionnaire. So, for example, I can associate 3 different species with questionnaire id=1, but how do I include that, say 2 of the first species, 1 of the second and 4 of the third were caught?

Check this: Extra fields on many-to-many relationships

Define another models Caught to hold the information per catch. Give it a related_name to make it easier to refer to in your code. You might also want to unique_together appropriate fields.
class Species(models.Model):
name = models.CharField(max_length=255, unique=True)
def __unicode__(self):
return '%s/%d' % self.name
class Questionaire(models.Model):
pass
class Caught(models.Model):
species = models.ForeignKey(Species)
number = models.IntegerField()
questionaire = models.ForeignKey(
Questionaire, related_name='catches')
def __unicode__(self):
return '%s/%d' % (self.species.name, self.number)
Use it like this:
(InteractiveConsole)
>>> from app.models import *
>>> s1 = Species(name='Salmon')
>>> s1.save()
>>> s2 = Species(name='Mackerel')
>>> s2.save()
>>> q = Questionaire()
>>> q.save()
>>> c1 = Caught(species=s1, number=7, questionaire=q)
>>> c2 = Caught(species=s2, number=5, questionaire=q)
>>> c1.save()
>>> c2.save()
>>> q.catches.all()
[<Caught: Salmon/7>, <Caught: Mackerel/5>]
>>>

Related

How can I access a ManyToManyField in the save method of a model?

I know that there's no way I'm the first to ask this question, but I cannot find the answer so please don't eat me.
If I have a model with a field that is a ManyToManyField; I know that I can get the selected objects prior to the save but this isn't helpful if the selected objects have changed.
models.py:
class AddOn(models.Model):
name = models.CharField(max_length=100)
class Invoice(models.Model):
additional_options = models.ManyToManyField(AddOn)
def save(self, *args, **kwargs):
super().save()
# this prints the objects that were selected prior to the save.
# this isn't helpful if the selected objects have changed.
print(self.additional_options.all())
Based on the way you worded your question, I think you are confused about what is actually happening behind the scenes with a ManyToManyField (I will refer to this as M2M from now on), so I will attempt to explain it, with the following models:
class Topping(models.Model):
name = models.CharField(max_length=30)
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
When we declare a M2M field, behind the scenes Django creates a through model which in this case looks like this:
class ThroughModel(models.Model):
topping = models.ForeignKey(Topping)
pizza models.ForeignKey(Pizza)
This is used to link the models together, and isn't stored in the database, unless you use the through keyword in the M2M field to create your own.
Before you can actually link the Pizza and Topping models in the M2M, you
must already have instances saved:
>>> t1 = Topping(name='pepperoni')
>>> t2 = Topping(name='sausage')
>>> t3 = Topping(name='ham')
>>> Topping.objects.bulk_create([
>>> t1, t2, t3
>>> ])
>>>
>>> pizza = Pizza(name='meat lovers')
Now, how can we add data to the M2M field? Like this:
>>> pizza.toppings.add(t1, t2, t3)
But as is, this would raise:
ValueError: 'Pizza' instance needs to have a primary key value before a many-to-many relationship can be used.
Because the pizza instance wasn't saved.
>>> pizza.save()
>>> pizza.toppings.add(t1, t2, t3)
>>>
>>> print('no error thrown now!')
You can also add toppings by passing in a primary key instead of an instance:
>>> pk = Toppings.objects.first().pk
>>> pk
1
>>> pizza = Pizza.objects.create(name='pepperoni pizza')
>>> pizza.toppings.add(pk)
Finally, to get the toppings from a given pizza instance:
>>> pizza.toppings.all()
<QuerySet [<Toppings: Toppings object (1)>, <Toppings: Toppings object (2)>, <Toppings: Toppings object (3)>]>
It's important to note that fetching the values from a M2M require 2 database queries, and there really isn't a way around this.
Lastly, to actually answer your question, you can do something like this to access a M2M field inside of save:
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
def save(self, *args): # args will be M2M data
super().save() # now that the instance is saved, we can access toppings
if args:
self.toppings.add(*args)
print(self.toppings.all())
Example usage:
>>> pizza = Pizza(name='sausage and ham')
>>> pizza.save(2, 3)
<QuerySet [<Toppings: Toppings object (2)>, <Toppings: Toppings object (3)>]>
# this is from the print inside of save
My example models come from the prefetch_related section of the docs, but I'd recommend reading through this section if you want to learn more about ManyToManyFields.

Django self-referential foreign key with related_name doesn't work

I followed this question to create a model pointing to itself with a ForeignKey. I just added the related_name option. The model looks like this:
class Person(models.Model):
name = models.CharField(max_length=100)
best_friend = models.ForeignKey('self', blank=True, null=True,
related_name='considered_best_friend_for')
def __unicode__(self):
return self.name
Then I did the following:
>>> a = Person.objects.create(name='Albert')
>>> a.save()
>>> j = Person.objects.create(name='John')
>>> j.save()
>>> m = Person.objects.create(name='Mark')
>>> m.save()
>>> a.best_friend = m
>>> a.save()
>>> j.best_friend = m
>>> j.save()
>>> m.considered_best_friend_for.all()
[]
>>> m.considered_best_friend_for
<django.db.models.fields.related.RelatedManager object at 0x102503190>
I don't understand why when I query m.considered_best_friend_for.all() I get an empty result []. I was expecting to get [<Albert>, <John>]. However, if I query m.considered_best_friend_for I get the RelateManager object.
Where is the mistake?
Note: I tried the same code changing the ForeignKey for a ManyToMany relationship and it worked, but in this case I need a ForeignKey relationship because a person can be considered best friend for more than one person (or zero), but can only consider one (or zero) person his/her best friend.
Thank you!
Mistake found! I was using a custom manager (needed for another part of the code) that was filtering out the results. Sorry about that! :)

Django queryset. Convert output to list

class Box(models.Model):
owner = models.ForeignKey(User, related_name='user', verbose_name='Owner')
name = models.CharField("Name", max_length=30)
items = models.ManyToManyField('Item', through='BoxItem', blank=True, null=True)
class BoxItem(models.Model):
item = models.ForeignKey('Item')
box = models.ForeignKey('Box')
quantity = models.IntegerField()
class Item(models.Model):
name = models.CharField("Name Item", max_length=30)
In [1]: from project.app.models import *
In [2]: b = Box.objects.get(owner=1)
In [3]: b.items_all()
Out[4]: [<Item: PC>, <Item: Notebook>]
How to convert this Output to list?
How to add to this list quantity next to the name of Item? (Example: PC - 3, Notebook- 5)
What about something like:
b = Box.objects.get(owner=1)
[('%s - %s' % (box_item.item.name, box_item.quantity)) for box_item in b.items_all()]
In line 4 it appears that the result of b.items_all() is actually a QuerySet; you can use any of the QuerySet's methods; for most purposes (including list access) I usually use the QuerySet directly.
In order to have quantity shown next to the name of Item at Python shell level you can use the __unicode__ method at Model level; this should be easy to achieve if you have a one-to-one relationship between Item and BoxItem; recall that this would result in an additional join each time the __unicode__ function is called.

Django- Get Foreign Key Model

How can I Get A Foreign Key Model Type?
For Example:
class Category(models.Model):
name = models.CharField(max_length = 100)
class SubCategory(models.Model):
category = models.ForeignKey(Category)
title = models.CharField(max_length = 100)
I Want To Get category Model In SubCategory.
How Can I Do It?
Try:
subcategory = SubCategory.objects.get(pk=given_pk)
subcategory.category
EDIT:
subcategory._meta.get_field('category').rel.to
For Django>=2.0
>>> SubCategory._meta.get_field('category').remote_field.model
>>> 'my_app.models.Category'
To get the model name use the __name__ class property.
>>> SubCategory._meta.get_field('category').remote_field.model.__name__
>>> 'Category'
ForeignKeys are ReverseSingleRelatedObjectDescriptor objects. So that's what you are really working with. You'll get that if you run type(SubCategory.category). From here you can use two ways to get the actual Class/Model referred to.
SubCategory.category.field.rel.to # <class 'path.to.Model'>
SubCategory.category.field.rel.to.__name__ # 'Category'
# or these will do the same thing
SubCategory._meta.get_field('category').rel.to
SubCategory._meta.get_field('category').rel.to.__name__
If you don't know the attribute name until run-time, then use getattr(SubCategory, attributeNameVariable) to get your ReverseSingleRelatedObjectDescriptor object for that ForeignKey field.
also for django > = 2.0
>>> SubCategory._meta.get_field('category').related_model
>>> <class 'my_app.models.Category'>
>>> SubCategory._meta.get_field('category').related_model._meta.model_name
>>> 'category'

How to maintain insert ordering for one-to-many relationship in Django

How to maintain insert ordering with one-to-many Django mapping, for eg:
Say we have,
class Person(models.Model):
name = Model.CharField(max_length=50)
class Subject(models.Model):
sub_name = Model.CharField(max_length=50)
person = Model.ForeignKey('Person')
def insert_data():
person = Person.objects.create(name='Deepan')
Subject(name='Eng', person=person).save()
Subject(name='Sci', person=person).save()
Subject.objects.filter(person=person) # is there a way to make this to always return the subjects in the inserted order, i.e Eng, Sci instead of Sci, Eng
This is handled using list types in hibernate/grails
Define the ordering using meta info:
class Subject(models.Model):
sub_name = models.CharField(max_length=50)
person = models.ForeignKey('Person')
time = models.DateTimeField(auto_now_add = True)
class Meta:
ordering = ['time'] #or ['-time'] according to the ordering you require
This will save the creation datetime in the time field and hence the results will be ordered according to addition time.
btw (if there are some other reasons) from your models it seems there will be many Persons and many Subjects so I suggest using many to many fields. This will map multiple users to multiple subjects and symmetrically back. You may even use the through option to store more details (time etc for sorting, even marks/percentage for storing records if you require to do that) per Person-Subject mapping.
You need to use a meta class to always sort it and use a date time field with the auto_now option set to True.
I recommend using a proxy class for the sorting. Sorting is an expensive operation.
See this link for more details on sorting and this link on proxy models.
class Meta:
ordering = ['id']
And no additional fields required. Django model always has 'id' field.
I was looking for exactly the same and to order_with_respecto_to works:
class Game(models.Model):
title = models.CharField(max_length=255)
class Task(models.Model):
title = models.CharField(max_length=255)
game = models.ForeignKey(Game, on_delete=models.CASCADE)
class Meta:
order_with_respect_to = 'game'
You can see the original order as they where inserted and you can modify it if you want. Also you can get the next register in sequence
>>> from prueba.models import Game,Task
>>> a=Game.objects.get(id=1)
>>> a.get_task_order()
<QuerySet [1, 2, 3]>
>>> a.set_task_order([3,2,1])
>>> a.get_task_order()
<QuerySet [3, 2, 1]>
>>> t=Task.objects.get(id=2)
>>> t.get_next_in_order()
<Task: task1>
>>> t=Task.objects.get(id=3)
>>> t.get_next_in_order()
<Task: task2>
Here is the documentation
https://docs.djangoproject.com/en/3.0/ref/models/options/#order-with-respect-to