extra context in django generic.listview - django

So I have two models: Car and Picture. a car may have multiple pictures.
Now I want to use a list view to display all the cars along with one picture for each car, can someone tell me how can I do that?
Below is my code
# models.py
class Car(models.Model):
name = models.CharField(max_length=100)
class Picture(models.Model):
car = models.ForeignKey(Car,related_name='pictures')
picture = models.ImageField()
# views.py
class CarList(ListView):
model = Car

List view has a method get_context_data. You can override this to send extra context into the template.
def get_context_data(self,**kwargs):
context = super(CarList,self).get_context_data(**kwargs)
context['picture'] = Picture.objects.filter(your_condition)
return context
Then, in your template you can access picture object as you wish.
I guess this should solve your problem.

As I wanted to pass forms with queryset, following worked for me.
def get_queryset(self):
return Men.objects.filter(your condition)
def get_context_data(self,**kwargs):
context = super(Viewname,self).get_context_data(**kwargs)
context['price_form']=PriceForm(self.request.GET or None)
return context
Using get_queryset() you can define a base queryset that will be implemented by get_context_data() in super().

You can directly access the related Picture objects to each Car object by using car.pictures.all in your template.
So you could do something like,
{% for car in objects %}
{{ car.name }}
{% if car.pictures.all %}<img src="{{ car.pictures.all.0.picture.url }}" />{%endif %}
{% endfor %}
For more info, read up on Related Objects.

Related

How to get annotated attributes in a template from a DetailView?

I'm working on a small e-commerce. For a model 'Product' I keep track of the stock using a Custom Manager and a method called Products.objects.with_stock() that uses annotations (I'm required to do so, there's no way I can add a stock attribute in Product)
In a ListView of all the products, it's pretty straightforward to add the stock, to access it from a template:
# app/views.py
class ProductListView(ListView):
...
def get_stock(self, obj):
return obj.stock
def get_queryset(self):
return super().get_queryset().with_stock()
And then in the template I just call it:
<!-- templates/home.html-->
{% for product in products %}
<p> Stock: {{product.stock}} <p>
{% endfor %}
How do I perform something similar for a DetailView?
Given that a DetailView gets a particular object instance, where or how do I run something similar to what I did in the ListView? Where do I run that query method that affects all objects as a whole, so that then I can access it in the same way from the template?
It couldn't be simpler: just do the same in the DetailView:
# app/views.py
class ProductDetailView(DetailView):
...
def get_stock(self, obj):
return obj.stock
def get_queryset(self):
return super().get_queryset().with_stock()

Django many-to-many making too many calls

I have a simple m2m relationship as below:
class Category(ModelBase):
name = models.CharField(max_length=255)
icon = models.CharField(max_length=50)
class Course(ModelBase):
name = models.CharField(max_length=255, unique=True)
categories = models.ManyToManyField(Category, related_name="courses")
I am using ListView to show all the courses in a category or all courses if no category provided.
views.py
class CourseListView(ListView):
model = Course
paginate_by = 15
template_name = "courses.html"
context_object_name = "courses"
def get_queryset(self):
queryset = (
super()
.get_queryset()
.select_related("tutor")
.prefetch_related("categories")
.filter(active=True)
)
category_id = self.kwargs.get("category_id")
return (
queryset
if not category_id
else queryset.filter(categories__in=[category_id])
)
def get_context_data(self, *args, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
category_id = self.kwargs.get("category_id")
if category_id:
context["current_category"] = Category.objects.get(id=category_id)
context["categories"] = Category.objects.all()
return context
Django is making duplicate calls as I am doing something like this in the template.
<div class="icon"><span class="{{ course.categories.first.icon }}"></span></div>
Not sure why, help much appreciated. Thanks!
When you do .prefetch_related('categories') the result of this prefetch will be used when you access course.categories.all. Any other queryset on course.categories will do a fresh query. Since course.categories.first is a new queryset, it does not use the prefetched result.
What you want to access in your template is the first result from course.categories.all(). But this is not easy in the template. I would recommend a method on the Course model:
class Course(...):
...
def first_category(self):
# Equivalent to self.categories.first(), but uses the prefetched categories
categories = self.categories.all()
if len(categories):
return categories[0]
else:
return None
And then in your template you can call this method
<div class="icon"><span class="{{ course.first_category.icon }}"></span></div>
You can also access the first value like:
{{ course.categories.all.0.icon }}
It is not necessary to write a method.
because categories is ManyToMany which means one category may appear in many courses, but in the template you just calling the first category's icon, so there maybe more than two course with the same first category, and it will retrieve them all, i recommend using another for loop to loops through categories too.
{% for course in courses %}
<div>
<h1>{{ course.name</h1>
......
<h4>categories</h4>
{% for category in course.categories %}
<div class="icon"><span class="{{ category.icon }}"></span></div>
{% endfor %}
</div>
{% endfor %}

Get queryset by using filter objects on template in django

In models:
class Match(models.Model):
hot_league = models.ManyToManyField(HotLeague, blank=True)
class HotLeague(models.Model):
user = models.ManyToManyField(User, blank=True)
price_pool = models.IntegerField()
winner = models.IntegerField()
In Views:
match = get_object_or_404(Match, pk=pk)
Here i need to access this Match queryset.
that's why
In template:
{% for hot_league in match.hot_league.all %}
By writing match.hot_league.all in template I can get all queryset of HotLeague class. But I want to use filter here with user. Like in views we can use HotLeague.objects.filter(user=request.user). But {% for hot_league in match.hot_league.filter(user=request.user) %} is not working on template.
How can I do that kind of filter in template?
How can I do that kind of filter in template?
Templates are deliberately restricted to avoid that. Some template processors, like Jinja can make function calls, but usually if you have to do that, something is wrong with the design. Views should determine what to render, and templates should render that content in a nice format.
In your view, you thus can render this as:
def some_view(request, pk):
match = get_object_or_404(Match, pk=pk)
hot_leagues = match.hot_league.filter(user=request.user)
return render(
request,
'some_template.html',
{'match': match, 'hot_leagues': hot_leagues}
)
In your template, you can then render this like:
{% for hot_league in hot_leagues %}
<!-- -->
{% endfor %}

Django filter ModelFormSet field choices... different from limiting the Formset's queryset

I understand that it is possible to override the default queryset 'used' by the modelformset. This just limits the objects for which a form is created.
I also found a Stack Overflow question about filtering ForeignKey choices in a Django ModelForm, but not a ModelForm Set and about limiting available choices in a Django formset, but not a Model FormSet. I have included my version of this code below.
What I want to do is render a ModelFormSet, for a school class ('teachinggroup' or 'theclass' to avoid clashing with the 'class' keyword) with one field limited by a queryset. This is for a teacher's class-editing form, to be able to reassign pupils to a different class, but limited to classes within the same cohort.
My models.py
class YearGroup(models.Model):
intake_year = models.IntegerField(unique=True)
year_group = models.IntegerField(unique=True, default=7)
def __unicode__(self):
return u'%s (%s intake)' % (self.year_group, self.intake_year)
class Meta:
ordering = ['year_group']
class TeachingGroup(models.Model):
year = models.ForeignKey(YearGroup)
teachers = models.ManyToManyField(Teacher)
name = models.CharField(max_length=10)
targetlevel = models.IntegerField()
def __unicode__(self):
return u'Y%s %s' % (self.year.year_group, self.name)
class Meta:
ordering = ['year', 'name']
My views.py
def edit_pupils(request, teachinggroup):
theclass = TeachingGroup.objects.get(name__iexact = teachinggroup)
pupils = theclass.pupil_set.all()
PupilModelFormSet = modelformset_factory(Pupil)
classes_by_year = theclass.year.teachinggroup_set.all()
choices = [t for t in classes_by_year]
# choices = [t.name for t in classes_by_year] #### I also tried this
if request.method == 'POST':
formset = PupilModelFormSet(request.POST,queryset=pupils)
if formset.is_valid():
formset.save()
return redirect(display_class_list, teachinggroup = teachinggroup)
else:
formset = PupilModelFormSet(queryset=pupils)
for form in formset:
for field in form:
if 'Teaching group' == field.label:
field.choices = choices
return render_to_response('reassign_pupils.html', locals())
As you can see, I am limiting the choices to the queryset classes_by_year, which is only classes which belong to the same year group. This queryset comes out correctly, as you can see in the rendered page below, but it doesn't affect the form field at all.
My template
{% for form in formset %}
<tr>
{% for field in form.visible_fields %}
<td> {# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
<p><span class="bigtable">{{ field }}</span>
{% if field.errors %}
<p><div class="alert-message error">
{{field.errors|striptags}}</p>
</div>
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit changes"></p>
</form>
{{ choices }} <!-- included for debugging -->
The page renders with all teaching groups (classes) visible in the select widget, but the tag at the bottom of the page renders as: [<TeachingGroup: Y8 82Ma2>, <TeachingGroup: Y8 82Ma3>], accurately showing only the two classes in Year 8.
Note that I've also read through James Bennett's post So you want a dynamic form as recommended by How can I limit the available choices for a foreign key field in a django modelformset?, but that involves modifying the __init__ method in forms.py, and yet the only way I know how to create a ModelFormSet is with modelformset_factory, which doesn't involve defining any classes in forms.py.
Further to help from Luke Sneeringer, here is my new forms.py entry. After reading Why do I get an object is not iterable error? I realised that some of my problems came from giving a tuple to the field.choices method, when it was expecting a dictionary. I used the .queryset approach instead, and it works fine:
class PupilForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PupilForm, self).__init__(*args, **kwargs)
thepupil = self.instance
classes_by_year = thepupil.teaching_group.year.teachinggroup_set.all()
self.fields['teaching_group'].queryset = classes_by_year
class Meta:
model = Pupil
As best as I can tell, you've actually put all the pieces together except one. Here's the final link.
You said you read the dynamic form post, which involves overriding the __init__ method in a forms.Form subclass, which you don't have. But, nothing stops you from having one, and that's where you can override your choices.
Even though modelformset_factory doesn't require an explicit Form class (it constructs one from the model if none is provided), it can take one. Use the form keyword argument:
PupilModelFormset = modelformset_factory(Pupil, form=PupilForm)
Obviously, this requires defining the PupilForm class. I get the impression you already know how to do this, but it should be something like:
from django import forms
class PupilForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PupilForm, self).__init__(*args, **kwargs)
self.fields['teaching_group'].choices = ______ # code to generate choices here
class Meta:
model = Pupil
The last problem you might have is that a modelformset_factory just takes the class, which means that the constructor will be called with no arguments. If you need to send an argument dynamically, the way to do it is to make a metaclass that generates the form class itself, and call that metaclass in your modelformset_factory call.
You can accomplish this by setting field choices of a form in a formset is in the forms init and overwriting the self.fields['field_name'].choices. This worked fine for me but I needed more logic in my view after the formset was initialized. Here is what works for me in Django 1.6.5:
from django.forms.models import modelformset_factory
user_choices = [(1, 'something'), (2, 'something_else')] # some basic choices
PurchaserChoiceFormSet = modelformset_factory(PurchaserChoice, form=PurchaserChoiceForm, extra=5, max_num=5)
my_formset = PurchaserChoiceFormSet(self.request.POST or None, queryset=worksheet_choices)
# and now for the magical for loop and override each desired fields choices
for choice_form in my_formset:
choice_form.fields['model'].choices = user_choices
I wasn't able to find the answer for this but tried it out and it works in Django 1.6.5. I figured it out since formsets and for loops seem to go so well together :)

How do i display inlines with DetailView?

I have a Project model.
This model has Days which are inlines.
How do I display them using a DetailView?
My views.py looks like this:
class ProjectDetailView(DetailView):
queryset = Project.objects.all()
slug_field = 'slug'
template_name = 'projects/detail_project.html'
How do I pull through the Day inlines with this?
I've tried:
def get_context_data(self, **kwargs):
context = super(ProjectDetailView, self).get_context_data(**kwargs)
project = Project.objects.filter(slug=self.slug_field)
context['days'] = Day.objects.filter(project=project)
return context
But this doesn't work. Also it seems pointless that I'm using a Generic view but then doing a get_object_or_404 anyway to pull the Days out.
How do I do this properly?
There's no such thing as an inline model. There are inline forms, which are forms for a model which has a ForeignKey relationship with a parent model - but you don't seem to be talking about forms.
In any case, there's no need to do anything in code. You can refer to the related models directly in the template:
{% for day in object.day_set.all %}
{{ day.whatever }}
{% endfor %}