Getting and displaying related objects in Django - python

I know this is simple, but I can't get my head around how to join some models together to display in my template in Django. I have "groups" that can have several "contacts".
So far I've got:
class Group(models.Model):
group_name = models.CharField()
class Contact(models.Model):
contact_name = models.ForeignKey(Group)
In my view, at first I assumed that simply getting my groups would also get any attached contacts, however that doesn't appear to be happening as expected:
def get_queryset(self):
groups = Group.objects.all()
return groups
I was expecting to do something like this in my template:
{% for group in groups %}
<h2>{{ group.group_name }}</h2>
{% for c in group.contact %}
<h3>{{ c.contact_name }}</h3>
{% endfor %}
{% endfor %}
This isn't working - what am I doing wrong? What is the correct query in my view to make sure the contact(s) for each group is getting retrieved?

Well, it looks like you've got some of your code from a different place so just so you can fully understand, you can do this in 2 different ways:
1) To access a related object of any kind, being a simple ForeignKey or ManyToMany you just need to go from the opposite model and use _set like this example:
class Group(models.Model):
group_name = models.CharField()
class Contact(models.Model):
contact_name = models.ForeignKey(Group)
{{ group.contact_set.all }}
2) You can set up a name different than the default _set changing Contact like this:
class Contact(models.Model):
contact_name = models.ForeignKey(Group, related_name='contacts')
So, related_name kwarg set a new name for you instead of the _set one:
{{ group.contacts.all }}
I hope I manage to make it clearer about simple access on models related objects.

Related

Is it possible to create a for loop inside a main for loop inside django templates?

I'm just learning django part time and I'm stuck on a problem that I just can't seem to find a solution to. I feel it is probably pretty simple, but I just can't seem to see it right now.
In django I have the following table
class category(models.Model):
category = models.CharField(max_length=100)
def __str__(self):
return f"{self.id} is {self.category}"
class knowledge(models.Model):
name = models.CharField(max_length=16)
location = models.URLField(max_length=1024)
title = models.TextField(blank=True, max_length=512)
description = models.TextField(max_length=1024)
category = models.ForeignKey(category, on_delete=models.CASCADE, related_name="content")
def __str__(self):
return f"{self.id}{self.name} {self.location} {self.description}"
class notes(models.Model):
note = models.TextField(blank=True, max_length=1024, default='none')
date_added = models.DateTimeField(blank=True, default=timezone.now)
knowledge = models.ForeignKey(knowledge, on_delete=models.CASCADE, related_name="mynotes")
def __str__(self):
return f"Added on {self.date_added}"
For my views I have
def kbs(request, category_id):
# get all the objects in the category table with the id specified
cate = category.objects.get(id=category_id)
# once you have the category id get all the objects from the knowledge table using the related name content
know = cate.content.all()
# for each id in know create a list of notes?
return render(request, "knowledge/category.html", {"cate": cate, "know": know,})
I can loop over all the items in the knowledge table and display them in my template. I wanted to add some notes to each knowledge article from another table. I added the notes table and referenced the knowledge table. In the template instead of displaying the id, I would like to loop over all the notes associated with that id and list those notes there.
here is my template
{% for knowledge in know %}
<h5{{knowledge.title}}</h5>
<p>{{ knowledge.description|linebreaks }}</p>
<p> {{ knowledge.id }}</p>
{{knowledge.name}}
{% endfor %}
I imagine there are two things that I need to do. I need to pass in a list of all the notes to my template from my views, then when it comes to the id I should be able to use templating to list all the items in my notes for that particular knowledge id. Perhaps a for loop nested in the main loop. I'm sure there must be an easy solution, I just can't seem to be able to figure it out. Can anyone assist?
Use the value of the related_name attribute to get the ForeignKey values of an object, in your template do something like this:
{% for knowledge in know %}
{{ knowledge }}
{% for note in knowledge.mynotes.all %} # btw I recommend renaming the 'mynotes' to something that is easier to understand like just 'notes'
{{ note }}
{% endfor %}
{% endfor %}

Creating Forms dynamically from a model

I'm learning Django as I go right now, but I'm trying to do something more dynamically.
Basically I've got multiple models defined as so:
class Group(models.Model):
name = models.CharField(max_length=255)
type = models.CharField(max_length=255) # Ignore this, is used somewhere else
class User(models.Model):
name = models.CharField(max_length=255)
group = models.ForeignKey(Group, verbose_name='group', on_delete=models.CASCADE)
(there are more models than these two)
I feel like writing a different view for each of these models isn't really a Django way to do it, but I'm struggling to find a different way. Basically I want to automatically create forms depending on what fields the model has.
So on /department/ there should be an text input for name and type.
But on /user/ there should be an text input for name and an selection for group.
All that rather in the same template. If possible ofcourse.
If it isn't possible, what's the best way to do this? Since creating a different template for each model doesn't seem right.
EDIT:
I've now got my CreateView working (very basic still). Now I want to create a ListView in a similar fashion. I've seen this issue, Iterate over model instance field names and values in template but it wasn't able to help me out...
forms.py
class ModelCreate(CreateView):
fields = '__all__'
template_name = 'polls/models/model_create.html'
def get_form(self, form_class=None):
try:
self.model = apps.get_model('polls', self.kwargs['model'])
except LookupError:
raise Http404('%s is not a valid model.' % self.kwargs['model'])
return super(ModelCreate, self).get_form(form_class)
model_create.html
{% extends 'polls/core.html' %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
{% endblock %}
Would it be possible to create a ListView in a similar fashion? So that I get field names and values dynamically (bit like {{ form }})

Django: Using backwards relationships in views.py

I'm trying to pass an object to my HTML template consisting of the parent object and all child objects that relate to it. For instance:
Model Chamber:
class Chamber(models.Model):
chamber_name = models.CharField(max_length=100)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
Has ChamberProperty:
class ChamberProperty(models.Model):
chamber = models.ForeignKey(Chamber, on_delete=models.CASCADE)
property_name = models.CharField(max_length=50)
property_value = models.CharField(max_length=100)
user_defined = models.BooleanField(default=True)
They are two separate models because the customer can add as many properties as they want to their chamber.
In my views.py
class ChambersView(generic.DetailView):
template_name = 'pages/chambers.html'
def get(self, request):
user = User.objects.get(username=request.user)
customer = Customer.objects.get(user=user)
chambers_list = list(Chamber.objects.filter(customer=customer))
try:
chamber_properties = list(ChamberProperty.objects.filter(chamber__in=chambers_list).order_by('id'))
except:
chamber_properties = "No properties"
form = ChambersFilterForm(request=request)
return render(request, self.template_name, {'filter_form':form, 'chambers_list': chambers_list, 'chamber_properties': chamber_properties})
Now, that does get me a list of all chambers, and a list of all chamber properties. Except they're not linked to each other. I'm not sure how to build a list of related objects. I read about backwards relationships just now, but I don't seem to grasp how to use them.
I tried the following:
chambers_and_props = Chamber.chamberproperty_set.all()
And I get the following error:
AttributeError: 'ReverseManyToOneDescriptor' object has no attribute 'all'
So I'm not quite sure how to use it. The threads I saw mentioned that a relationship in Django automatically add a reverse on ForeignKeys and the usage should be parent.child_set.all() with child in lower caps.
I get a ReverserManyToOneDescriptor object, but not sure how to turn that into a usable list I can pass on to my HTML template.
Any ideas as to what I'm doing wrong?
Your query does not work because you have not specified which Chamber you want to get the backwards relation for.
However, this is not the right approach. Presumably you want the ChamberProperty so you can list them against each Chamber in the template. So, you should follow the relationship there in the template - there's no need to query ChamberProperty separately in the view at all.
{% for chamber in chamber_list %}
{{ chamber.chamber_name }}
{% for property in chamber.chamberproperty_set.all %}
{{ property.property_name }} : {{ property.property_value }}
{% endfor %}
{% endfor %}
You are getting the error because you are trying Chamber.chamberproperty_set on the model Chamber. It will work on individual chamber instances:
You can do this in the view:
for chamber in chambers_list
properties = chamber.chamberproperty_set.all()
Or in the template:
{% for chamber in chambers_list %}
{{ chamber }}
{% for property in chamber.chamberproperty_set.all %}
{{ property }}
{% endfor %}
Then, in your view, you can use prefetch_related to reduce the number of SQL queries:
chambers_list = Chamber.objects.filter(customer=customer).prefetch_related('chamberproperty_set')

Django order_with_respect_to reverse foreign_key

I got troubles on CBW with Django 1.8. I use CBW, and if possible I'd like to avoid using views at all. But I'm stuck in a attempt to order the results I have with default queries.
so, more infos and the example : working on django 1.8.3
Got a few models, bands, that can go touring on events, in different venues
myapp/models.py >
from django.db import models
class Band(models.Model):
name = models.CharField(max_length=512)
class Venue(models.Model):
name = models.CharField(max_length=512)
class Meta:
order_with_respect_to = 'VenueEvents'
class Event(models.Model):
datetime = models.DateTimeField()
name = models.CharField(max_length=512)
venue = models.ForeignKey(Venue, trough='VenueEvents')
band = models.ForeignKey(Band)
class Meta:
ordering ['venue', 'datetime']
myapp/view.py >
#Empty
myapp/url.py >
from django.conf.urls import url, patterns
from django.views.generic import DetailView
from myapp.models import Band
urlpatterns = patterns('',
url(r'^bands/(?P<pk>[\d]+)$', DetailView.as_view(model=Band), name='venue')
and then the template file directly :
template/myapp/venue_detail.html >
<html><body><ul><li>
{{ band.name }}
{% for event in band.event_set.all %}
{% ifchanged event.venue %}
{% if not forloop.first %}
</li><li>
{% endif %}
{{ event.datetime }}
{% endif %}
{{ event.name }}
{% endfor %}
</li></ul></body></html>
I want on that page to have all the venues a band toured at listed, and I want them ordered by event_set.datetime. Meaning the first Venue listed is the one where they last played at.
I tryied a few different things, all not working :
adding through parameters to the ForeignKey. did not work
trying to stick the logic in the template. did not work (and who would do that ? )
having in Venue : ordering ['event_set']
So, I'd really like to have a solution with no view.py, but only if it is possible. So, what would be the best idea to solve this ?
Maybe is there a simple way to add a new table to help me order those things ?
Maybe can i add a method on my class Venue that would accept a band as argument and would be the thing i can order at, except django doesn't allow to order thing by a method.
Maybe I have to override the objects manager to add this possibility, but is there an easy way to do this ?
Or is just the only solution to skip views-less CBW ?
Or is there any other solution ?
Seems like you are using order_with_respect_to wrong.
You should probably order_with_respect_to = 'venue' on Event instead.
Have a look at https://docs.djangoproject.com/en/1.8/ref/models/options/#order-with-respect-to to see how it works.

Django relational database in view

I have a django application where I have few manytomany fields and im trying to show these relation records in my listing, however it all comes blank
so this is my model:
class Genre(models.Model):
genre_name = models.CharField(max_length=50)
genre_slug = models.SlugField(max_length=300)
genre_meta = models.TextField(max_length=300)
genre_description = models.TextField(max_length=300)
listing = models.BooleanField(default=True)
def __unicode__(self):
return self.genre_name
class Movie(models.Model):
movie_name = models.CharField(max_length=50)
movie_slug = models.SlugField(max_length=300)
movie_description = models.TextField(null=True, max_length=300)
movie_category = models.ManyToManyField(Category, related_name='category')
movie_genre = models.ManyToManyField(Genre, blank=True, related_name='genre')
listing = models.BooleanField(default=True)
def __unicode__(self):
return self.movie_name
this is my view
def AllMovies (request):
movies= Movie.objects.all().order_by('movie_name')
context = {'movies': movies}
return render_to_response('all.html', context, context_instance=
RequestContext(request))
and this is my template
{% for movie in movies %}
{{ movie }}
{% for genre in movies.genre.all %}{{ genre_name }}{% endfor %}
{% endfor %}
so there are three concerns here:
1- All I get is blank in my template
2- What would be the best option to show items which have been ticked as listed and hide the rest in template
3- This is a data bank so im sure we will need to use load more button in my template, but if i want to show 30 of items instead of all
Try this:
{% for movie in movies %}
{{ movie }}
{% for genre in movie.movie_genre.all %}{{ genre_name }}{% endfor %}
{% endfor %}
You were trying to iterate over movies.genre.all, which is wrong because:
movies is a queryset, you should use movie instead, as it is a Model instance
genre is not a field in the Movie model, you should use movie_genre, which is a ManyToManyField you are looking for
I believe you didn't understand the related_name attribute. It doesn't modify the behavior of the field (in this case: movie_genre). Instead, it changes the name of an attribute in a related model (in this case: Genre). For example, you can use it to get all related movies:
>>> genre = Genre.objects.get(name='Drama')
>>> genre.genre.all()
[<Movie>, <Movie>, ...]
As you can see, your choice of a related_name wasn't so good. Something like movies would be more appropriate.
Please, read the documentation once again, as you probably didn't understand Django's logic around relations (foreign keys, many-to-many, etc.).
Besides, here are a few style-related tips:
don't use movie_ and genre_ prefixes in field names - e.g. it is obvious that genre_name is a name of a genre, since it is defined inside Genre model
try to follow PEP8 (http://legacy.python.org/dev/peps/pep-0008/), as it is a standard in the Python community
read about class-based views (https://docs.djangoproject.com/en/dev/topics/class-based-views/) - you may find them very helpful

Categories

Resources