How to get many to many value in Django template - django

I have 2 app: authors_app and books_app
authors_app.models:
class Author(models.Model):
name = models.CharField(max_length=250, unique=True)
books_app.models:
class Book(models.Model):
author = models.ManyToManyField(Author, related_name='authors')
Authors_app Views.py
class AuthorBio(DetailView):
model = Author
template_name = 'author-bio.html'
Problem
I need to get all the book published by the author.
In my template I try:
{% for book in author.books.all %}
...
{% endfor %}
But this doesn't work
Question
How can I get all the books by the author

First change
author = models.ManyToManyField(...)
to
authors = models.ManyToManyField('authors_app.author', related_name="books")
now you can access books using related_name: author.books.all()
Code Example:
>>> from authors_app.models import Author
>>> Author.objects.create(name="J.K. Rowling")
<Author: J.K. Rowling>
>>> from books_app.models import Book
>>> jk = Author.objects.first()
>>> jk
<Author: J.K. Rowling>
>>> Book.objects.create(name="Harry Potter: Chamber of secrets")
<Book: Harry Potter: Chamber of secrets>
>>> hp = Book.objects.first()
>>> hp.authors.set([jk])
>>> jk.books.all()
<QuerySet [<Book: Harry Potter: Chamber of secrets>]>
>>>
Pay attention to your queryset performance.

Related

Filter a contenttype with list of content object's field

I creating an app where users can post with its related tags:
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
class Post(models.Model):
user = models.ForeignKey(User)
body = models.TextField()
tags = models.ManyToManyField(Tag)
pub_date = models.DateTimeField(default=timezone.now)
activity = GenericRelation(Activity, related_query_name="posts")
class Photo(models.Model):
user = models.ForeignKey(User)
file = models.ImageField()
tags = models.ManyToManyField(Tag)
pub_date = models.DateTimeField(default=timezone.now)
activity = GenericRelation(Activity, related_query_name="photos")
class Activity(models.Model):
actor = models.ForeignKey(User)
verb = models.PositiveIntegerField(choices=VERB_TYPE)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
pub_date = models.DateTimeField(default=timezone.now)
What I want to do is get/filter maximum of 5 latest/recent Activity objects, with the a list of users, and a list of tags from the list of Post objects tags field and return json using django-rest-framework to view in the client side.
Eg activities:
UserA created a new Post obect with tags(#class, #school)
UserB created a new Post object with tags(#professor, #teacher)
UserC created a new Post object with tags(#school, #university)
UserD created a new Post object with tags(#university, #school)
So say I want to filter Activity with user_list=[UserA, UserC] and tag_list = [#class, #teacher]
It should return:
UserA created a new Post obect with tags(#class, #school)
UserC created a new Post object with tags(#school, #university)
UserB created a new Post object with tags(#professor, #teacher)
To filter the Activity with users, I can query this way:
Activity.objects.filter(actor__in=user_list)
But, how do I filter Activity with the content_object's (i.e.Post or Photo) field (i.e. Post.tags or Photo.tags)? Now I am doing this way:
Activity.objects.filter(posts__tags__in=tag_l)
Activity.objects.filter(photos__tags__in=tags)
So to sum up, If I have need activities with list of users and list of tags I have to do like this:
activites = Activity.objects.filter(
Q(actor__in=user_list) |
Qposts__tags__in=tag_list) |
Q(photos__tags__in=tag_list)
)
But suppose there will be more than two ContentType model classes then I'd have to again add another Q(moreModel__tags__in=tag_list). So, I hope there's a better way to optimize the process.
I'd say your best bet would be to use a filter from Django filter (link to the rest framework docs), specifically a ModelMultipleChoiceFilter. I'm going to assume you already have an ActivityViewSet to go along with the Activity model.
Firstly you'll want to create a django_filters.FilterSet, probably in a new file such as filters.py, and set up the ModelMultipleChoiceFilter, like so:
import django_filters
from .models import Activity, Tag, User
class ActivityFilterSet(django_filters.FilterSet):
tags = django_filters.ModelMultipleChoiceFilter(
name='content_object__tags__name',
to_field_name='name',
lookup_type='in',
queryset=Tag.objects.all()
)
users = django_filters.ModelMultipleChoiceFilter(
name='content_object__user__pk',
to_field_name='pk',
lookup_type='in',
queryset=User.objects.all()
)
class Meta:
model = Activity
fields = (
'tags',
'users',
)
Then you'll want to tell your viewset to use that filterset:
from .filters import ActivityFilterSet
# ...
class ActivityViewSet(GenericViewSet):
# all your existing declarations, eg.,
# serializer_class = ActivitySerializer
# ...
filter_class = ActivityFilterSet
# ...
Once you've got that done, you'll be able to filter your results using GET parameters, eg.,
GET /activities?users=1 – everything created by User 1
GET /activities?users=1&users=2 – everything created by User 1 or User 2
GET /activities?users=1&tags=class – everything created by User 1 with the tag #Class
GET /activities?users=1&users=2&tags=class&tags=school – everything created by User 1 or User 2 with the tags #Class or #School
and so on
I will give you an example from django updown, it have a similiar model: https://github.com/weluse/django-updown/blob/master/updown/models.py#L31
>>> from updown.models import Vote
>>> Vote.objects.first()
<Vote: john voted 1 on Conveniently develop impactful e-commerce>
>>> v = Vote.objects.first()
>>> v.content_object
<Thread: Conveniently develop impactful e-commerce>
>>> v.content_object.__class__
<class 'app_forum.models.Thread'>
>>> [ v.content_type for v in Vote.objects.all() if v.content_object.__class__.__name__ == 'Thread' ]
[<Thread: Conveniently develop impactful e-commerce>, <Thread: Quickly evisculate exceptional paradigms>]
>>>
# You can also use with
>>> user_ids = [ u.content_type.id for u in Vote.objects.all() if u.content_object.__class__.__name__ == 'User' ]
>>> user_ids
[1, 52, 3, 4]
>>> from django.contrib.auth.models import User
>>> User.objects.filter(pk__in=user_ids)
[<User: John>, <User: Alex>, <User: Roboto>, <User: Membra>]
>>>
>>> from django.contrib.contenttypes.models import ContentType
>>> ContentType.objects.get_for_model(v.content_object)
<ContentType: Detail Thread>
>>>
You also can use ContentType.objects.get_for_model(model_instance) such as this: https://github.com/weluse/django-updown/blob/master/updown/fields.py#L70
In your problem, maybe can use with this..
>>> photo_ids = [ ac.content_type.id for ac in Activity.objects.all() if ac.content_object.__class__.__name__ == 'Photo' ]
>>> Activity.objects.filter(content_type__id__in=photo_ids)
# or
>>> photo_ids = [ ac.content_type.id for ac in Activity.objects.all() if content_type.model_class().__name__ == 'Photo']
>>> Activity.objects.filter(content_type__id__in=photo_ids)
Hope it can help..
For this method set the related_query_name to the name of the model in lower case or verbose_name of the model.
You can first filter out the content type present in the Activity model.
content_types = ContentType.objects.filter(activity__id__isnull=False)
Now use these content types to build the lookup.
q = Q(actor__in=user_list)
for content_type in content_types:
arg = content_type.name + '__tags__in'
kwargs = {arg: tag_list}
q = q | Q(**kwargs)
Now you can filter the activities using this lookup.
activities = Activity.objects.filter(q).distinct()

get_object_or_404 with relationship

I suppose this must be possible judging by this post but I can't seem to work out the syntax. I need to get the blog object with the picture field via the author foreign key.
Is this possible with get_object_or_404, and if so how?
#models.py
class Blog(models.Model):
author = models.ForeignKey(MyUser)
#author
class MyUser(AbstractUser):
picture = models.ImageField()
blog = get_object_or_404(Blog, pk=blog_id)
The query you want is like so (from the docs):
from django.db import models
class City(models.Model):
# ...
pass
class Person(models.Model):
# ...
hometown = models.ForeignKey(City)
class Book(models.Model):
# ...
author = models.ForeignKey(Person)
And then your query is:
b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author # Doesn't hit the database.
c = p.hometown # Doesn't hit the database.
b = Book.objects.get(id=4) # No select_related() in this example.
p = b.author # Hits the database.
c = p.hometown # Hits the database.
except in your case your query would be: Picture.objects.select_related('author__blog').get(picture_name='somefilename.jpg')
You're saying I want the picture (and while your at it, the Author and their Blog) that relates to the picture name 'somefilename.jpg'. At least if I've understood your question and DB structure it is.
As Pureferret suggested in his answer
you can do it in following way,
blog = Blog.objects.get(pk=blog_id)
author = blog.author
and if you want to show that image in template just use
{{ auther.picture.url }}

Django ManyToManyField Retrieving both objects error

I implemented a ManyToManyField for a following feature which allows users to follow other users and if they follow other user .
They would able to retrieve their's objects.
This is my module
class Person(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100, blank=True)
image = models.FileField(upload_to="images/",blank=True)
following = models.ManyToManyField('self', related_name='followers', symmetrical=False, blank=True, null=True)
birthday = models.DateField(blank=True,null=True)
def __unicode__(self):
return self.name
class Board(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
picture = models.OneToOneField('Picture',related_name='picture',blank=True,null=True)
def __unicode__(self):
return self.name
The problem is when a user is following 2 users . I am only able to retrieve a single user objects instead of both users.
For example I created 3 users , Jimmy , Sarah and Simon . Sarah is following Simon and Jimmy
Jimmy has a board called Jimmy's Math Board ,Jimmy English Board
Simon has a single board called Simon's Whiteboard
The problem is underneath
>>> from pet.models import *
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(username='Sarah')
>>> person = Person.objects.get(user=user)
>>> sarah = Person.objects.get(user=user)
>>> sarah.following.all() # SARAH IS FOLLOWING JIMMY AND SIMON
[<Person: Jimmy>, <Person: Simon>]
>>> users = User.objects.filter(pk__in=sarah.following.all().values_list('user__pk',flat=True))
>>> users
[<User: Jimmy>, <User: Simon>] # I'm able to retrieve their users
>>> board = Board.objects.filter(user=users) # when I search for their boards , I only retrieve a single user . Jimmy's board not Simon
>>> board
[<Board: Jimmy's Math Board>, <Board: Jimmy English Board>]
>>> person = Person.objects.filter(user=users)
>>> person
[<Person: Jimmy>]
How Can I retrieve both of these users board?
Because board = Board.objects.filter(user=users) is filtering by user it expects one user to be provided. If you were to do something like board = Board.objects.filter(user__in=users) which uses the __in filter, the filtering will correctly use the list of user objects.
You could also use a flat list of id's instead of objects like so board = Board.objects.filter(user__in=sarah.following.all().values_list('user__pk',flat=T‌​rue))

Django QuerySet Error

I have a simple module below and I have a field called following in the Person module.
class Person(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
image = models.FileField(upload_to="images/",blank=True,null=True)
following = models.ManyToManyField('self', related_name='followers', symmetrical=False, blank=True, null=True)
def __unicode__(self):
return self.name
I created 3 accounts called sarah , jim and ben. I managed to get Sarah to follow jim and ben but when I display who sarah followed , it return a querysets not their object themself . How can I work in reverse to get them into objects themself so I can relate them to my Person module and display the names of the people who sarah are following.
This is the part I don't understand
>>> from pet.models import *
>>> from django.contrib.auth.models import User
>>> sarah = User.objects.get(username='Sarah')
>>> jim = User.objects.get(username='Jim')
>>> ben = User.objects.get(username='Ben')
>>> Sarah = Person.objects.get(user=sarah)
>>> Jim = Person.objects.get(user=jim)
>>> Ben = Person.objects.get(user=ben)
>>> Sarah.following.add(Jim,Ben) //setup the many to many relationship
>>> Sarah.save()
>>> Sarah.following.all() // return a queryset of Person objects which john is following.
[<Person: Jim>, <Person: Ben>]
I want to return all the user objects that user are following in my template.
#SI Eric
>>> Sarah.following.all().values_list('name',flat=True)
[u'Jim', u'Ben']
>>> p = Sarah.following.all().values_list('name',flat=True)
>>> person = Person.objects.filter(user=p)
>>> print person
class Board(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
Sarah.following.all().values_list('name', flat=True)
would output
['Jim', 'Ben']
As bernie suggested, you can set the unicode function and the default queryset returned will do the same. In this particular type of instance I prefer to be more explicit with what I'm getting from the objects, but that's just me.
Edit after question was updated:
I'm not sure I follow what you're trying to do. You have a queryset of the objects of the people who are following sarah. If you just want the names, the values list above will work. If you want to iterate over each individual object then you would do something like this:
for person in Sarah.following.all():
# person is an instance of Person
# you can access all the properties of the object by accessing person
print person.name
Alternatively, pass the entire queryset to the template by adding it to the context in the view:
context['following'] = Sarah.following.all()
Then in the template you could do this (for example):
<ul>
{% for person in following %}
<li>{{ person.name }}</li>
{% endfor %}
</ul>
Edit:
Both from within the template and the view code you can access any of the properties of each 'Person' object by just directly referencing them. So for example if you want to access the properties of the 'User' object that person has a Foreign Key relation to, you would just access it's properties directly, like so:
person.user.username
person.user.email
etc...
One last edit:
To get a queryset of 'User' objects based on the 'following` queryset for a person, you could do this:
users = User.objects.filter(pk__in=Sarah.following.all().values_list('user__pk', flat=True)
From there you should be able use that filter boards and other objects that reference the User model:
boards = Board.objects.filter(user__in=users)

Django many-to-many relationship with extra fields

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>]
>>>