I have 3 models, Entry model and Category model, and I have created intermediate model CategoryEntry.
class Entry(models.Model):
entry_text = models.TextField()
user = models.ForeignKey(User)
class Category(models.Model):
user = models.ForeignKey(User)
category_text = models.CharField(max_length=200)
entries = models.ManyToManyField(Entry, through='CategoryEntry')
class CategoryEntry(models.Model):
category = models.ForeignKey(Category)
entry = models.ForeignKey(Entry)
viewed = models.BooleanField(default=False)
I have created a View with get_queryset method like this
def get_queryset(self):
order_by = self.request.GET.get('order_by')
if not order_by:
order_by = '-pub_date'
return Entry.objects.filter(category__id=self.kwargs.get('category_id', None), category__user__id=self.request.user.id).order_by(order_by)[:].select_related('user')
I have category ID from kwargs. The problem is - how to get every entries related field "viewed", so I can show its value in template. For example related User field I can get like
{% for entry in entries %}
{{ entry.entry_text }}
{{ entry.user.name }}
{% endfor %}
What I need is to access "viewed" field something like "entry.categoryentry.viewed"
Tried prefetch_related, but doesn't seem to work or don't get how to know right name to access field in template
Entry.objects.filter(category__id=self.kwargs.get('category_id', None), category__user__id=self.request.user.id).order_by(order_by)[:].select_related('user').prefetch_related(Prefetch("categoryentry_set", queryset=CategoryEntry.objects.filter(category__id=self.kwargs.get('category_id', None))))
Thanks!
You would do:
{% for entry in entries %}
{{ entry.entry_text }}
{{ entry.user.name }}
{% for ce in entry.catalogentry_set.all %}
{{ce.category.category_text}}
{# or whatever.. #}
{% endfor %}
{% endfor %}
Basically, if no related_name is present, you could access the reverse lookup element by lowercase model name + _set - which returns a queryset manager object.
Related
Suppose that we have following models
class Category(models.Model):
name = models.CharField(max_length=254)
class Item(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="items")
name = models.CharField(max_length=254)
state = models.ForeignKey(State, on_delete=models.CASCADE)
Categories and their items are listed like so
def view(request):
categories = Category.objects.all()
pass
{% for category in categories %}
{{ category.name }}
{% for item in category.items.all %}
{{ item.name }}
{% endfor %}
{% endfor %}
In this structure, I want to write on-request filtering for listed 'items'.
def view(request):
...
queryset = ???
state = request.GET.get('state')
if state:
queryset = queryset.filter(state__name=state)
The problem is defining 'queryset'. Because, Items are listed as related objects of category.
Can it be done properly? or Do I need to change design?
You can take a look at my design to be more clear.
Low fidelity design
Your related_name in the Item object for category field should be named "items". Then category.items.all will give you the list of items in that category. Look at the example in the documentation.
class Tag(models.Model):
article = models.ForeignKey(
Article,
on_delete=models.CASCADE,
related_name="tags",
)
name = models.CharField(max_length=255)
To filter items depending on category you can pass the PKs of the category in the request and filter according to those specific categories.
views.py
def your_view(request):
...
list_of_your_category_ids = [1,4] #get them as a list from frontend
list_of_your_state_names = [1,2]
queryset = Item.objects.filter(
category__in=list_of_your_category_ids,
state__code__in=list_of_your_category_names
)
This will give you the queryset you wanted. Now all you need is to regroup this queryset with their categories.
Django provides a regroup template tag that does exactly this.
{% regroup queryset by category as categories_list %}
<ul>
{% for category in categories_list %}
<li>
{{category.grouper}}
<ul>
{% for item in category.list %}
<li>
{{item}}: state - {{item.state.code}}
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
https://i.stack.imgur.com/TbTpz.png
how to retrive data many to one relationship.
class Ads(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=75)
class Aimage(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
ads = models.ForeignKey(Ads, on_delete=models.CASCADE, related_name='ads_image')
image = models.ImageField(blank=True)
my view:
qs = Ads.objects.all()
template:
{% for o in qs.aimage_set.all %}
{{ o.title }}
{{ o.image.url }} #does work
{% endfor %}
qs is a set of Ads, so you can not access qs.aimage_set on the queryset but on an Ads model. Furthermore you specified as value for the related_name=… parameter [Django-doc], related_name='ads_image', so this it the name of the manager to access the objects in reverse:
{% for ad in qs %}
<b>{{ o.title }}</b>
{% for o in ad.ads_image.all %}
{{ o.image.url }}
{% endfor %}
{% endfor %}
This will result in an N+1 problem. You can use .prefetch_related(…) [Django-doc] to fetch the related Aimage objects in bulk:
qs = Ads.objects.prefetch_related('ads_image')
I have a Django Post model with a fk to a Category model with a category name and a category slug. Now I filter these posts by category via django-filter and the LinkWidget. That's fine and worked via name="categories__name" out of the box:
# models.py
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
class Post(models.Model):
categories = models.ManyToManyField(Category)
# filters.py
class PostFilter(django_filters.FilterSet):
categories = django_filters.AllValuesFilter(
name="categories__name",
label="Categories",
widget=LinkWidget(),
)
class Meta:
model = Post
fields = ['categories',]
# template.html
<h2>Filter</h2>
{% for choice in filter.form.categories %}
{{ choice }}
{% endfor %}
<h2>Posts</h2>
<ol>
{% for obj in filter.qs %}
<li>{{ obj.title }}</li>
{% endfor %}
But now I would like to eliminate the categories__name as the GET parameter and use categories__slug instead - but keep the more human readable categories__name as the link text in the LinkWidget - but I have no idea how to achieve this, any hints?
Some renderd html snippets:
Filtering via name=categories__slug:
<ul id="id_categories">
<li>audio-and-video</li>
<li>bits-bytes</li>
<li>foo-and-bar</li>
</ul>
Filtering via name=categories__name:
<ul id="id_categories">
<li>Audio and Video</li>
<li>Bits/Bytes</li>
<li>Foo and Bar</li>
</ul>
But what I want is (pseudocode):
<li>{{ category.name }}</li>
... and rendered:
<ul id="id_categories">
<li>Audio and Video</li>
<li>Bits/Bytes</li>
<li>Foo and Bar</li>
</ul>
Perhaps it's easier to review this issue in a minimal, complete django project, so I made one: https://gitlab.com/tombreit/django-filter-demo
Versions:
Python 3.5.3
Django (1.10.7)
django-filter (1.0.4)
Try this solution:
class CustomAllValuesFilter(django_filters.ChoiceFilter):
#property
def field(self):
qs = self.model._default_manager.distinct()
qs = qs.order_by(self.name).values_list(self.name, self.extra['choice_name'])
del self.extra['choice_name']
self.extra['choices'] = list(qs)
return super(CustomAllValuesFilter, self).field
class PostFilter(django_filters.FilterSet):
categories = CustomAllValuesFilter(
name="categories__slug",
label="Categories",
widget=LinkWidget(),
choice_name="categories__name"
)
class Meta:
model = Post
fields = ['categories',]
How can I show in a template the attributes related by a ManyToManyField of a model?
models.py
class Group(models.Model):
name = models.CharField()
user = models.ManyToManyField(User, related_name='user_group')
class Team(models.Model):
name = models.CharField()
group = models.ManyToManyField(Group, related_name='group_team')
views.py
class Index(ListView):
template_name = 'App/index.html'
model = User
def get_queryset(self):
return User.objects.filter(...)
template
{% for user in user_list %}
{{ user.username }}
{{ user.user_group.name }}
{{ user.user_group.group_team.name }}
{% endfor %}
I can show the username of the user but not the rest of the fields.
Since a ManyToMany relationship may have many objects all I had to do is iterate over them:
{% for group in user.user_group.all %}
{{ group.name }}
{% endfor %}
Update: As Todor suggests adding prefetch_related avoid unnecessary database queries
User.objects.filter(...).prefetch_related('user_group')
Lets say I have a setup like this:
class Product(models.Model):
name = models.CharField()
category = models.CharField()
class Shop(models.Model):
name = models.CharField()
website = models.URLField()
class ProductPrices(model.Model):
product = models.ForeignKey(Product)
shop = models.ForeignKey(Shop)
price = models.FloatField()
updated = models.DateTimeField()
and then I have a page on which I would like to list all products, which shops they can be found at and to what prices. Is it possible to have the prices for all (or just some) products sorted, without using:
class Meta:
ordering = ['-price']
on ProductPrices?
That is, in my view I would want to do something like this (sort the productprice_set, on the price column):
def get_queryset(self):
queryset = Product.objects.all()
for product in queryset:
product.productprice_set.order_by('-price')
# Have also tried the following, without any result
product.productprice_set = product.productprice_set.order_by('-price')
return queryset
To clarify: I want to sort the prices for each product, not the products based on the prices. But I want to do this by going "through" the products way.
I want 'productprice_set' ordered, and not 'product'.
That syntax does not somehow modify the relationship so that subsequent calls return an ordered queryset: it returns the ordered qs then and there. Subsequent calls will have the default ordering.
The way to do this is to use the order_by clause when you're retrieving the objects. Since you can't pass arguments to methods in the template, the way to do it is to define a method on Product which returns the ordered prices:
class Product(models.Model):
...
def ordered_prices (self):
return self.productprices_set.order_by('-price')
and call it in the template:
{% for product in products %}
{{ product.name }}
{% for price in product.ordered_prices %}
{% price.price %}
{% endfor %}
{% endfor %}
I have found a solution now in which I don't have to modify any model, nor make any more database queries.
In my view I have now:
def get_queryset(self):
queryset = Product.objects.all().prefetch_related('price_set')
for product in queryset:
product.prices = sorted(product.price_set.all(), key=lambda x: x.price, reverse=true)
return queryset
And then in my template:
{% for product in products %}
{{ product.name }}
{% for price in product.prices %}
{% price.price %}
{% endfor %}
{% endfor %}
Since I'm new to using Django, I wonder if there's anything "wrong" or bad by solving it like this?