Django: Create Submodel in Model - python

Suppose I have two models, Book and Page, such there is a one-to-many relationship between Page to Book. I would like each Book model, to have a function which creates a bunch of Pages for the respective Book model, but I'm not sure how to associate each Page's foreign key back to the Book instance it was created in.
I currently have an associate function, which finds the correct book amongst all the possible books, and I'm sure there's a better way, but I can't seem to find it. Is it possible to set the foreignkey to self?

class Page(models.Model):
# default related_name would be `page_set` but `pages`
# seems a bit more intuitive, your choice.
book = models.ForeignKey("yourapp.Book", related_name="pages")
class Book(models.Model):
# ...
# you can use self as the instance and magically you have
# this `pages` attr which is a related manager, like `Page.objects`
# but limited to the particular Book's pages.
def special_page_create(self, *args, **kwargs):
self.pages.create(
# ...
)
# ...
then, you have a book instance, you have a related manager,
book = Book.objects.get(...
book.pages.create(...
page, created = book.pages.get_or_create(...
pages_gte_42 = book.pages.filter(num__gte=42)
# etc
let me know what else you need to know. The reverse relationship is semi-magical; you don't declare anything on the Book class but you get the related manager on the Book instances.

would try something like this:
Class Page(models.Model):
#Your fields, like a name or whatever
name = models.CharField(max_length=60)
Book= models.ForeignKey("Book")
Class Book(models.Model):
#Your fields again
def create_page(self):
"Adds a page to the book"
#Do what you need to do
page = Page(name="TitlePage", Book=self)
page.save()
Or something like that... I think the order of the classes is important too.

There is a one-to-many relationship between Book and Page. This means that one Book has several Pages and each Page only one Book.
In a database (and therefore in an ORM mapper) you would model this by creating a foreign key in Page. This foreign key would point to the Book instance that contains these Pages.
If you want to find the Pages for a Book, you'd query the Pages table for all Pages with the same foreign key as the primary key of the Book.

Related

Django many to many relation, include all IDs in queryset in both directions

I have 2 models connected via M2M relation
class Paper(models.Model):
title = models.CharField(max_length=70)
authors = models.ManyToManyField(B, related_name='papers')
class Author():
name = models.CharField(max_length=70)
Is there a way to include authors as all related authors' IDs (and maybe name somehow)?
Is there a way to include papers IDs as reverse relation (and maybe title as well)?
Author.objects.all().annotate(related_papers=F('papers'))
this only adds id of one paper, first one it finds I think.
Furthermore, changing related_papers to papers gives an error:
ValueError: The annotation ‘papers’ conflicts with a field on the
model.
From what I understand in your comments, you're using DRF. I will give you 2 answers.
1) If you're talking about model serializer, you can use PrimaryKeyRelatedField :
class AuthorSerializer(serializers.ModelSerializer):
papers=serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Author
fields = ['name', 'papers']
class PaperSerializer(serializers.ModelSerializer):
class Meta:
model = Paper
fields = '__all__'
This will return the IDs for the other side of the relationship whether you're on Paper or Author side. That will return the primary keys, not a representation of the object itself.
2) Now you're also talking about performance (e.g. database hit at each iteration).
Django (not DRF-specific) has a queryset method to handle preloading related objects. It's called prefetch_related.
For example, if you know you're going to need the relation object attributes and want to avoid re-querying the database, do as follow:
Author.objects.all().prefetch_related('papers')
# papers will be already loaded, thus won't need another database hit if you iterate over them.
Actually, it has already been implemented for you. You should include a Many-to-Many relationship to author in your Paper model like this:
class Paper(models.Model):
title = models.CharField(max_length=70)
authors = models.ManyToManyField(Author, related_name='papers')
That gives you the opportunity to add Author objects to a related set using
p.authors.add(u), assuming that p is the object of Paper model, and a is an object of Author model.
You can access all related authors of a Paper instance using p.authors.all().
You can access all related papers of an Author instance using u.papers.all().
This will return an instance of QuerySet that you can operate on.
See this documentation page to learn more.

How to access data across M2M tables in Django?

What is the 'best practice' way of accessing data across a 1 (or more) many-to-many tables?
This is incredibly difficult for me as I am not sure what I shuld be googling/looking up.
I have attached a diagram of my data model. I am able to query data for 'C' related ot a user, by utilizing serializers.
there has to be a simpler way of doing this (I'm hoping).
Doing it with serializers seems incredibly limiting. I'd like to access a user's 'B' and 'C' and transform the object to only have a custom structure and possible unique values.
Any direction is much appreciated. Pretty new to Django, so I apologize for this newb type of question.
Here is an example of M2M relation using Django:
class User(models.Model):
name = models.CharField(...)
class Song(models.Model)
title = models.CharField(...)
users_that_like_me = models.ManyToManyField('User', ..., related_name='songs_that_i_like')
So a User can like many Songs and a Song can be liked by many Users.
To see all the songs a user liked, we can do:
user = User.objects.get(id='<the-user-id>')
liked_songs = user.songs_that_i_like.all()
And to see all the users who like a particular song we can similarly do:
song = Song.objects.get(id='<the-song-id>')
users_that_like_this_song = song.users_that_like_me.all()
Both liked_songs and users_that_like_this_song are actually querysets, meaning we can do some Django magic on them.
For example, to find all users named Jon that liked this song we can do:
users_that_like_this_song.filter(name='Jon')
We can also add some property shortcuts to our Models to help with some common tasks, for example:
class User(models.Model):
...
#property
def number_of_liked_songs(self):
return self.songs_that_i_like.count()
Then we can do:
user = User.objects.get(id='<the-user-id>')
number_of_songs_i_like = user.number_of_liked_songs
There's much more we can do with Django - if you're looking for something specific let us know.

Django, update the object after a prefetch_related

I have the following models:
class Publisher(models.Model):
name = models.CharField(max_length=30)
class Book(models.Model):
title = models.CharField(max_length=100)
publisher = models.ForeignKey(Publisher)
In my views.py, When I want to show the publisher page, I also want to show their books, so I usually do something like this:
publisher = Publisher.objects.prefetch_related('book_set').filter(pk=id).first()
Then, after some processing I also do some work with the books
for book in publisher.book_set.all():
foo()
This works great, but I have one problem. If there is a book added between the query and the for loop, the publisher.book_set.all() won't have the newly added books because it was prefetched.
Is there a way to update the publisher object?
You can delete the entire prefetch cache on the instance:
if hasattr(publisher, '_prefetched_objects_cache'):
del publisher._prefetched_objects_cache
If you only want to delete a particular prefetched relation:
if hasattr(publisher, '_prefetched_objects_cache'):
publisher._prefetched_objects_cache.pop('book_set', None)
There's a nicer way to clear prefetched attributes, using only public Django APIs, which is the refresh_from_db() method:
# Reloads all fields from the object as well as clearing prefetched attributes
publisher.refresh_from_db()
# Clears one prefetched attribute, will be re-fetched on next access
publisher.refresh_from_db(fields=['book_set'])
for book in publisher.book_set.all():
Also there is possibility to drop all prefetch_related from Django doc:
To clear any prefetch_related behavior, pass None as a parameter::
non_prefetched = qs.prefetch_related(None)

In Django, how do I join a table with a composite primary key to another table?

Here's what I have in the way of models:
class Lead(models.Model):
user = models.ForeignKey(User, related_name='leads')
site = models.ForeignKey(Site)
...
class UserDemographic(models.Model):
user = models.ForeignKey(User, related_name='user_demographic')
site = models.ForeignKey(Site)
...
class Meta:
unique_together = 'user', 'site'
In the first model, we record leads on a per-site, per-user basis. There can be multiple leads from the same user on a given site. In the second model, we store each user's demographic data. For each site, each use has only one record of demographic data.
What I would like to be able to do is tack this demographic data onto our leads query. Each lead has both user and site, and I want to grab the data in the demographic table and pair it to the corresponding lead. So basically what I need here is a left join that will unite these two. This is simple enough to do when there is only one foreign key, but I have no clue how to make it work when there are two foreign keys on which to join the tables.
Any ideas on this? Is there even a way to do this in Django, or will I have to resort to a raw query? Thanks!
Django's ORM doesn't let you do this natively, but you can minimise your raw sql by using the extra method. Something like this should work:
Lead.objects.extra(tables=['appname_userdemographic'],
where=['appname_userdemographic.user_id=appname_lead.user_id',
'appname_userdemographic.site_id=appname_lead.site_id'],
select={'country': 'appname_userdemographic.country'})
Alternately, you could refactor your models so you don't need the composite key - for example, create a UserSite model and link your lead and demographic models to that.
class UserSite(models.Model):
user = models.ForeignKey(User)
site = models.ForeignKey(Site)
class Lead(models.Model):
user_site = models.OneToOneField(UserSite)
...
class UserDemographic(models.Model):
user_site = models.OneToOneField(UserSite)
...
Then you can use select_related, like so:
Lead.objects.select_related('usersite__userdemographic')

Django Form with a one-to-many relationship

I have a form in Django called PersonForm this forms model has a one-to-many relationship with Car. When displaying the PersonForm just like in the Django Admin I would like to allow my users to select/deselect from a list of Cars etc. Is this possible? I'm looking for information on where to start.
This is what I have so far for the PersonForm:
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('description',)
The Models:
class Person(models.Model):
description = models.CharField(max_length="150")
class Car(models.Model):
make = models.CharField(max_length="25")
owner = models.ForeignKey('Person', related_name="Car")
So in the person form I need to show a list of cars that person is the owner of an allow selecting/deselecting of them. I'm assuming I can do this in the form i.e. using something like the related name.
Sounds like you want an inline model form. This give you the ability to add/remove Car objects from a Person within the Person form.
That previous link was for inlinemodeladmin. This next link is for an inline form:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#modelforms-factory
I didn't have any chance with inline formset, so i would suggest to override your save method of the model, i feel it's more DRY:
class PersonForm(forms.ModelForm):
# add a field to select a car
car = forms.ModelChoiceField(car.objects.all())
class Meta:
model = Person
fields = ('description', 'car')
def save(self, commit=True):
instance = super().save(commit)
# set Car reverse foreign key from the Person model
instance.car_set.add(self.cleaned_data['car']))
return instance
I know this is an old thread, but since I found myself almost exclusively pointed here by google when searching, I thought I would include the following for anyone else looking for an answer.
The answer, I think, is to use
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelchoicefield
or
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelmultiplechoicefield
There is a good article on how to use the modelmultiplechoicefield at :
https://medium.com/swlh/django-forms-for-many-to-many-fields-d977dec4b024
But it works for one to many fields as well. These allow us to generate a form with multiple choices as checkboxes or similar widgets based upon a related field in a model.

Categories

Resources