Here is my issue. I am new to python/django (about 2 months in). I have 2 tables, Project and Status. I have a foreign key pointing from status to project, and I am looking to try to display the value of the foreign key (status) on my project template, instead of the address of the foreign key.
Here is my models.py
from django.db import models
from clients.models import Clients
from django.contrib.auth.models import User
from settings import STATUS_CHOICES
from django.db import models
from clients.models import Clients
from django.contrib.auth.models import User
from settings import STATUS_CHOICES
class Project(models.Model):
client = models.ForeignKey(Clients, related_name='projects')
created_by = models.ForeignKey(User, related_name='created_by')
#general information
proj_name = models.CharField(max_length=255, verbose_name='Project Name')
pre_quote = models.CharField(max_length=3,default='10-')
quote = models.IntegerField(max_length=10, verbose_name='Quote #', unique=True)
desc = models.TextField(verbose_name='Description')
starts_on = models.DateField(verbose_name='Start Date')
completed_on = models.DateField(verbose_name='Finished On')
def __unicode__(self):
return u'%s' % (self.proj_name)
class Status(models.Model):
project = models.ForeignKey(Project, related_name='status')
value = models.CharField(max_length=20, choices=STATUS_CHOICES, verbose_name='Status')
date_created= models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.value
class Meta:
verbose_name = ('Status')
verbose_name_plural = ("Status")
My views.py
#login_required
def addProject(request):
if request.method == 'POST':
form = AddSingleProjectForm(request.POST)
if form.is_valid():
project = form.save(commit=False)
project.created_by = request.user
project.save()
project.status.create(
value = form.cleaned_data.get('status', None)
)
return HttpResponseRedirect('/project/')
else:
form = AddSingleProjectForm()
return render_to_response('project/addProject.html', {
'form': form, 'user':request.user}, context_instance=RequestContext(request))
And finally my template:
{% if project_list %}
<table id="plist">
<tr id="plist">
<th>Quote #</th>
<th>Customer</th>
<th>Date</th>
<th>Project Name</th>
<th>Status</th>
<th>Contact</th>
</tr id="plist">
{% for p in project_list %}
<tr id="plist">
<td>{{ p.pre_quote }}{{ p.quote }}</td>
<td>{{ p.client }}</td>
<td>{{ p.starts_on }}</td>
<td>{{ p.proj_name }}</td>
<td>{{ p.status_set.select_related }}</td>
<td>{{ p.created_by }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>No projects available.</p>
{% endif %}
Any help would be much appreciated. Thank you!
I'm guessing you mean here:
<td>{{ p.status_set.select_related }}</td>
This doesn't do anything. select_related is an optimisation feature, it has nothing to do with actually getting or displaying the related content. If you want to do that, you will have to iterate through the result of p.status_set.all.
In your model, you have defined the related name for this ForeignKey as "status." Thus, you can now use "status" as this name instead of the "_set" business.
Since this is a ForeignKey (ManyToOne) field, you can't simply display the field as if there were only one value. Instead you need .all, which will return a queryset of all the statuses that point to the object in question. You can then iterate through these.
If instead, you know that each project will have only one status, you can use a OneToOne field instead of a ForeignKey.
Related
Problem:
I would like to assign a bank statement for each user. Each user will log in using their username and password, and access their document. At the moment, one user can see the statement of all users.
Option 1:
I`ve added a manytomany relationship for the user to the model. When logged in on the Admin interface, I can assign the Bank Statement to the user.
Question:
What is the right way to define the views.py or the html file to only show the bank statement to the user logged-in.
models.py
class BankStatement(models.Model):
user = models.ManyToManyField(settings.AUTH_USER_MODEL,related_name='bs')
name = models.CharField(max_length=20)
date_created = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
views.py
#login_required(login_url="/login/")
def bankstatement_detail(request, bankstatement_id):
bankstatement = BankStatement.objects.get(pk=bankstatement_id)
context = {
'bankstatement': bankstatement,
}
return render(request, 'administration/bankstatement_detail.html', context)
#login_required(login_url="/login/")
def index_bankstatement(request):
user = request.user
bankstatement = BankStatement.objects..filter(user=request.user)
context = {
'bankstatement': bankstatement,
}
return render(request, 'administration/bankstatement_detail.html', context)
bankstatement_detail.html
<div class="card-block table-border-style">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Date Created</th>
<th>Last Updated</th>
</tr>
</thead>
<tbody>
{% for bankstatement in bankstatements %}
<tr>
<td>{{ bankstatement.id }}</td>
<td>{{ bankstatement.name }}</td>
<td>{{ bankstatement.date_created }}</td>
<td>{{ bankstatement.last_updated }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
Many Thanks
I do not know if you are still working on this one, but
I do not agree with linking of the BankStatement to the User as ManyToMany. In my opinion, one user can have many statements, but one statement has only one user. So it is a one to many relationship defined by Foreign Key.
Let's fix the view:
#login_required(login_url="/login/")
def bankstatement_detail(request, bankstatement_id):
# this line forces to show the statement of the logged in user
user = request.user
bankstatement = BankStatement.objects.get(pk=bankstatement_id, user=user)
context = {'bankstatement': bankstatement,}
return render(request, 'administration/bankstatement_detail.html', context)
I am trying to optimize a Django project (vers. 1.8.6) in which each page shows 100 companies and their data at once. I noticed that an unnecessary amount of SQL queries (especially with contact.get_order_count) are performed within the index.html snippet below:
index.html:
{% for company in company_list %}
<tr>
<td>{{ company.name }}</td>
<td>{{ company.get_order_count }}</td>
<td>{{ company.get_order_sum|floatformat:2 }}</td>
<td><input type="checkbox" name="select{{company.pk}}" id=""></td>
</tr>
{% for contact in company.contacts.all %}
<tr>
<td> </td>
<td>{{ contact.first_name }} {{ contact.last_name }}</td>
<td>Orders: {{ contact.get_order_count }}</td>
<td></td>
</tr>
{% endfor %}
{% endfor %}
The problem seems to lie in constant SQL queries to other tables using foreign keys. I looked up how to solve this and found out that prefetch_related() seems to be the solution. However, I keep getting a TemplateSyntaxError about being unable the parse the prefetch, no matter what parameter I use. What is the proper prefetch syntax, or is there any other way to optimize this that I missed?
I've included relevant snippets of model.py below in case it's relevant. I got prefetch_related to work in the defined methods, but it doesn't change the performance or query amount.
model.py:
class Company(models.Model):
name = models.CharField(max_length=150)
def get_order_count(self):
return self.orders.count()
def get_order_sum(self):
return self.orders.aggregate(Sum('total'))['total__sum']
class Contact(models.Model):
company = models.ForeignKey(
Company, related_name="contacts", on_delete=models.PROTECT)
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=150, blank=True)
def get_order_count(self):
return self.orders.count()
class Order(models.Model):
company = models.ForeignKey(Company, related_name="orders")
contact = models.ForeignKey(Contact, related_name="orders")
total = models.DecimalField(max_digits=18, decimal_places=9)
def __str__(self):
return "%s" % self.order_number
EDIT:
The view is a ListView and defines the company_list as model = Company. I altered the view based on given suggestions:
class IndexView(ListView):
template_name = "mailer/index.html"
model = Company
contacts = Contact.objects.annotate(order_count=Count('orders'))
contact_list = Company.objects.all().prefetch_related(Prefetch('contacts', queryset=contacts))
paginate_by = 100
Calling the get_order_count and get_order_sum methods causes one query every time the method is called. You can avoid this by annotating the queryset.
from django.db.models import Count, Sum
contacts = Contact.objects.annotate(order_count=Count('orders'), order_sum=Sum('orders'))
You then need to use a Prefetch object to tell Django to use your annotated queryset.
contact_list = Company.objects.all().prefetch_related(Prefetch("contacts", queryset=contacts)
Note that you need to add the prefetch_related to your queryset in the view, it is not possible to call it in the template.
Since you are using ListView, you should be overriding the get_queryset method, and calling prefetch_related() there:
class IndexView(ListView):
template_name = "mailer/index.html"
model = Company
paginate_by = 100
def get_queryset(self):
# Annotate the contacts with the order counts and sums
contacts = Contact.objects.annotate(order_count=Count('orders')
queryset = super(IndexView, self).get_queryset()
# Annotate the companies with order_count and order_sum
queryset = queryset.annotate(order_count=Count('orders'), order_sum=Sum('orders'))
# Prefetch the related contacts. Use the annotated queryset from before
queryset = queryset.prefetch_related(Prefetch('contacts', queryset=contacts))
return queryset
Then in your template, you should use {{ contact.order_count }} instead of {{ contact.get_order_count }}, and {{ company.order_count }} instead of {{ company.get_order_count }}.
Try this in views.py
company_list = Company.objects.all().prefetch_related("order", "contacts")
I have a model that has a ManyToManyField of Users:
models.py
class Excuse(models.Model):
students = models.ManyToManyField(User)
reason = models.CharField(max_length=50)
views.py
def excuse_list(request, block_id=None):
queryset = Excuse.objects.all().prefetch_related('students')
context = {
"object_list": queryset,
}
return render(request, "excuses/excuse_list.html", context)
Template (excuse_list.html)
...
{% for excuse in object_list %}
<tr>
<td>{{ excuse.reason }}</td>
<td>
{% for student in excuse.students.all %}
{{student.get_full_name}};
{% endfor %}
</td>
</tr>
{% endfor %}
...
How can I sort the User set (excuse.students.all) alphabetically by a User field, for example: user.last_name?
you can do this just changing your views.py with this:
queryset = Excuse.objects.all().prefetch_related(
Prefetch('students', queryset=User.objects.order_by('lastname')))
remember to import User model!
You can create a method in your Excuse model, that will return a sorted students set:
def get_sorted_students(self):
return self.students.all().order_by('last_name')
And then in your template use excuse.get_sorted_students instead of excuse.students.all
Alternatively, you could use a custom template tag to render the list of students.
If I have a one-to-many relationship model. How do i display items from the related child table in my parent template.
models.py
class Parent(models.Model):
name = models.CharField()
class Child(models.Model):
parent = models.ForeignKey(Parent)
child_name = models.CharField()
parent.html
{% for parent in parents %}
<tr>
<td>{{ parent.id }}</td>
<td>{{ parent.name }}</td>
<td>{{ child.child_name }}</td>
</tr>
{% endfor %}
Read the Django docs on following relationships backwards: https://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects
But basically, it's parent.child_set.all
You can change the name child_set by adding a related_name='children' to the ForeignKey: parent = ForeignKey(Parent, related_name='children')
Then you can use: parent.children.all
I've read in the docs and stackoverflow but i cant figure out why my foreign key following isnt working, django just says that there is no such attribute as vote_story_set.all().
I have a table named story and one named vote_story and the story table has several foreign keys like a foreign to vote_story to get the number of votes for a news story.
According to the docs and the detailed answer here: *_set attributes on Django Models i created an object like this:
all_stories = Story.objects.all()
votes = all_stories.vote_story_set.all()
but this doesnt work since django says that there is no such attribute as "vote_story_set". The database table for story has the 'votes' attribute as a foreign key to the table Votes_story. From the examples ive seen this should be working so i dont understand why it doesnt. There is no foreign keys in the Votes_story table, just a primay key and the attribute 'votes' containing the number of votes.
Update:
Models and template is shown below for Story and Vote_story as well as the relevant view.
Models:
class Story(models.Model):
story_id = models.IntegerField(primary_key=True)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
title = models.CharField(max_length=150)
description = models.CharField(blank=True, null=True, max_length=2000)
story_text = models.TextField()
picture = models.ImageField(blank=True, null=True, upload_to="story/images")
storyAccessLevelID = models.ForeignKey(StoryAccessLevel)
categoryID = models.ForeignKey(Category)
votes = models.ForeignKey(Vote_story, blank=True, null=True)
comments = models.ForeignKey(Comment, blank=True, null=True)
def __unicode__(self):
return self.title
class Vote_story(models.Model):
votes = models.IntegerField()
def __unicode__(self):
return self.votes
In the file Vote_story is defined above Story so it can find it.
In Vote_story i let django create the primary key automatically unlike Story.
There is currently one vote for one of the stories added.
Template code (the relevant portion):
<table class="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Date added</th>
<th>Votes</th>
<th>Comments</th>
</tr>
</thead>
<tbody>
{% for story in all_stories %}
<tr>
<td>{{ story.title }}</td>
<td>{{ story.description }}</td>
<td>{{ story.date_added }}</td>
<td>{{ story.votes }}</td>
<td>{{ story.comments }}</td>
</tr>
{% endfor %}
</tbody>
</table>
The view is like this:
def list_all_stories(request):
""" Show all stories """
all_stories = Story.objects.all()
return render(request, "base/story/all_stories.html", {'all_stories': all_stories})
all_stories is a queryset, not an instance. vote_story_set is an attribute on each instance within the queryset, not the queryset itself.
Plus, you seem to be confused about the direction of relations. If your ForeignKey goes from Vote to VoteStory, you don't need the reverse relation, you need the forward one, which is just whatever you called the field. Again, though, this is an attribute of each instance, not the queryset.