select backward associated in django - django

I'm wrinting a Django project.
In courses/models.py
class Category(models.Model):
title = models.CharField(max_length=50)
class Language(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
class Course(models.Model):
name = models.CharField(max_length=250)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
I want to get list of all Category and then loop through each Language of specified category.
class Courses(ListView):
template_name = 'courses/index.html'
model = Course
context_object_name = 'courses'
def get_context_data(self, **kwargs):
context = super(Courses, self).get_context_data(**kwargs)
categories = Category.objects.all()
context['categories'] = categories
return context
in template courses/index.html I want to show the list of languages based on category
{% for category in categories %}
{{ category.title }}
<li>lis of languages in this category</li>
{% endfor %}
How to loop on backward associated data?

Django creates a relation that is accessible in the templates through the modelname_set key.
In your case, you'd have to iterate over:
category.language_set.all
{% for category in categories %}
{{ category.title }}
{% for language in category.language_set.all %}
<li>{{ language.title }}</li>
{% endfor %}
{% endfor %}

_set does this work.
eg. in your case this code must work:
{% for language in category.language_set.all %}
{{ language.title }}
{% endfor %}

Related

Writing on-request filtering for related objects

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

Not able to display in Template the foreign key using ListView

I'm trying to display a photographic session with all of it photos, so I have the session name in one model and I have the pictures in
another model linked by a foreign key. I'm not able to display it in
the HTML I'm not sure if I'm using the ListView get_context_data
correctly and I'm certainly sure the the html code is not correct but
I have not found how to do it.
views.py
class SessionPictures(generic.ListView):
model = PostSession
template_name = 'photoadmin/gallery.html'
def get_context_data(self, **kwargs):
context = super(SessionPictures, self).get_context_data(**kwargs)
context['picture'] = Images.objects.all()
return context
models.py
class PostSession(models.Model):
session_name = models.CharField(max_length=25)
created_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return str(self.session_name)
class Images(models.Model):
name = models.ForeignKey(
PostSession, related_name='images', on_delete=models.CASCADE, null=True, blank=True)
picture = models.ImageField(upload_to='pictures')
html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<h2>Images</h2>
<ul>
{% for session in object_list %}
<li>{{ session.session_name }}</li>
<ul>
<li>{{session.picture_set.all.url}}</li>
</ul>
{% endfor %}
</ul>
{% endblock %}
I'm expecting this:
Woods
picture1.url
picture2.url
picture3.url
Beach
Picture4.url
picture5.rul
As you already defined related_name="images" in Images model, so, session.images_set attribute won't work with PostSession.
class Images(models.Model):
name = models.ForeignKey(
PostSession,related_name='images', on_delete=models.CASCADE, null=True, blank=True)
Instead, use session.image.all in template(FYI: it returns a queryset, so you need to iterate through it to get the image object):
{% for session in object_list %}
<li>{{ session.session_name }}</li>
<ul>
{% for i in session.images.all %}
<li> i.picture.url </li>
{% endfor %}
</ul>
{% endfor %}
More information on reverse relation can be found in documentation.

Display foriegnkey fields in Django template for a CreateView

I am trying to display a checklist in the CreateView using the values in the ForeignKey fields for descriptions.
models.py
class Structure(models.Model):
name = models.CharField(max_length = 30)
description =models.CharField(max_length = 300, null=True, blank=True)
def __str__(self):
return self.name
class SelectedFramework(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
structure = models.ForegignKey(Structure)
selected = models.BooleanField(default = False)
views.py
class FrameworkCreateView(generic.CreateView):
model = SelectedFramework
fields =['structure', 'selected']
template_name = 'catalogue/structure.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super(FrameworkCreateView, self).form_valid(form)
structure.html
{% extends 'catalogue\base.html' %}
{% block container %}
<h2>{% block title %}Structures{% endblock title %}</h2>
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
<div class="col-sm-10">{{form.structure}} {{form.selected}}</div><br>
{% endfor %}
</div>
</form>
{% endblock %}
The code above works but will display the ForeignKey 'structure' as a dropdown list with the values of __str__. Is there a way to display string for structure.name and structure.description with the checkbox from selected in the CreateView?
In your template use:
{{ form.structure.name }}
{{ form.structure.description}}
You can write custom form, override the save method and create Structure object manually there:
class FrameworkForm(forms.ModelForm):
structure_name = forms.CharField(required=True)
structure_description = forms.CharField(required=False)
class Meta:
model = SelectedFramework
fields = [
'structure_name', 'structure_description', 'selected'
]
def save(self, commit=False):
instance = super(FrameworkForm, self).save(commit=False)
structure = Structure(
name=self.cleaned_data.get('structure_name'),
description=self.cleaned_data.get('structure_description')
)
structure.save()
instance.structure = structure
instance.save()
return instance
Also add form_class = FrameworkForm to your view instead of fields = ['structure', 'selected']
EDIT:
Perhaps you want something like this:
<ul>
{% for structure in form.fields.structure.choices.queryset %}
<li>{{ structure.name }} - {{ structure.description }}</li>
{% endfor %}
</ul>
If you want to get fields by iterating in the template. You have to use-
{% for field in form %}
{{ field }}
{% endfor %}
don't have to use any dot notation to get the field. If you want to get the label of the field you can use {{ field.label}} usually before {{field}}

Django model relationships in views and templates

I'm working on multi-user rss reader. I want to limit display of posts only to those which are unread. I've managed to do this in my single "feed" view as below, but I can't figure out how to do the same in multiple feed aka "category" view.
I've been trying something like here https://docs.djangoproject.com/en/1.5/topics/db/queries/#spanning-multi-valued-relationships but it didn't work for me
Should I change my "category" view code or template code? and if so how would you go about it?
thanks!
-S
models
class UserCategory(models.Model):
name = models.CharField(unique=False, max_length=64)
user = models.ForeignKey(User)
slug = AutoSlugField(populate_from='name', always_update='True', unique_with='user')
class Feed(models.Model):
feed_url = models.URLField(unique=True)
default_title = models.CharField(max_length=64, blank=True)
link = models.URLField(blank=True)
class UserFeed(models.Model):
feed = models.ForeignKey(Feed)
title = models.CharField(max_length=64)
category = models.ForeignKey(UserCategory)
user = models.ForeignKey(User)
slug = AutoSlugField(populate_from='title', always_update='True', unique_with='user')
class Post(models.Model):
feed = models.ForeignKey(Feed)
title = models.CharField(max_length=256)
content = models.TextField()
link = models.URLField(max_length=512)
class ReadPost(models.Model):
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
views
def feed(request, user_feed_slug):
user_feed = get_object_or_404(UserFeed.objects.filter(slug=user_feed_slug, user=request.user))
read_post = ReadPost.objects.filter(user=request.user).values_list('post')
posts = Post.objects.select_related().filter(feed=user_feed.feed).exclude(id__in=read_post)
def category(request, user_category_slug):
user_category = get_object_or_404(UserCategory.objects.filter(slug=user_category_slug, user=request.user))
templates
feed
{% for post in posts %}
{{ post.title }}
{% endfor %}
category
{% for feed in user_category.userfeed_set.all %}
{{ feed.title }}
{% for post in feed.feed.post_set.all %}
{{ post.title }}
{{ post.content }}
{% endfor %}
{% endfor %}
You can write custom template filter, i.e:
#register.filter
def unread(posts, read_posts):
return posts.exclude(id__in=read_posts)
(before you must pass read_post to category template context).
Try this queryset:
def category(request, user_category_slug):
user_category = get_object_or_404(UserCategory, slug=user_category_slug,
user=request.user))
feeds = UserFeed.objects.filter(category__slug=user_category_slug, user=request.user)\
.prefetch_related('feed__post_set')
then in your template:
{% for feed in feeds %}
{{ feed.title }}
{% for post in feed.feed.post_set.all %}
{{ post.title }}
{{ post.content }}
{% endfor %}
{% endfor %}

Group django formset into groups based on foreign key

I have a simple setup of InventoryItems and Categories. I have a formset of InventoryItems but want to split up the items based on the FK Category, I don't need or want an inline form set.
Simplified version of what I have
class Category(models.Model):
name = models.CharField(max_length=255)
inventory = models.BooleanField(default=False)
class Inventory(models.Model):
name = models.CharField(max_length=255)
quantity = models.IntegerField()
category = models.ForeignKey(Category)
def viewOfForm(request):
categories = Category.objects.filter(inventory=True)
InventoryFormset = modelformset_factory(Inventory, can_delete=True, extra=1)
formset = InventoryFormset(request.POST or None, queryset=Inventory.objects.filter(category__inventory=True))
return render_to_response('template.html', locals())
What I would like to do in the template
{% for category in categories %}
<fieldset class="collapse">
<h2>{{ category.name }}</h2>
{% for form in formset %}
{% if form.category == category %}
{{ form }}
{% endif %}
{% endfor %}
</fieldset>
{% endfor %}
You only need a small change to get this working; use form.instance.category in your if template tag:
{% if form.instance.category == category %}