django: values() dictionnary with many to many relationship - django

I'm trying (for training purposes) to make an application for a library catalogue. I took the decision to make a Person model for all people involved in creation of the book and join it with Book model through intermediary model (since one book may have several authors, redactors, translators etc), which would describe the role of this person.
The Book model is for an edition of a book. It stores information about book as a piece of literature. For each edition of a book there is a book instance.
The copy model is for a book as an object.
Now I'm trying to make search function. My goal is to display a list of all book which meets search criteria, but have all copies which share the authors and the title (regardless the fact, if it was the same publisher, translator, etc), as one row with the number indicating the number of copies. So, I understand, I should use annotate with Count on values() dictionnary. But I'm not able to get all information I need while using values().
If I make a query searching by the book title, I have all creators in the values() dictionnary. But if the search criteria is author, the values dictionnary keeps only the name which was used to filter, so if the book has two or three authors, I don't get their names.
Here are my models (for Book model only the relevant fields, to make shorter):
class Person(models.Model):
name = models.CharField(max_length=128, blank=True)
born = models.CharField(max_length=64, blank=True)
class Book(models.Model):
title = models.CharField(max_length=255, blank=True)
creators = models.ManyToManyField(Person, through='Creator')
class Copy(models.Model):
on_shelf = models.BooleanField()
location = models.CharField(max_length=32, blank=True)
remarques = models.CharField(max_length=255, blank=True)
signature_mark = models.CharField(max_length=32, blank=True)
approuved = models.BooleanField(default=False)
collection = models.ForeignKey(Collection, on_delete=models.CASCADE, blank=True, null=True)
book = models.ForeignKey(Book, on_delete=models.CASCADE, null=True)
class Creator(models.Model):
AUTHOR = 'A'
TRANSLATOR = 'T'
REDACTION = 'R'
INTRODUCTION = 'I'
ROLE_CHOICES = [
(AUTHOR, 'Autor'),
(TRANSLATOR, 'Tłumacz_ka'),
(REDACTION, 'Opracowanie, redakcja [Nazwisko Imię]'),
(INTRODUCTION, 'Wstęp, posłowie'),
]
person = models.ForeignKey(Person, on_delete=models.CASCADE)
book = models.ForeignKey(Book, on_delete=models.CASCADE, blank=True, null=True)
role = models.CharField(max_length=32, blank=True, choices=ROLE_CHOICES)
Here are my queries:
By title:
book = Copy.objects.filter(book__title='Rozwój cywilizacji amerykańskiej tom 1-2').values('book__creators__name', 'book__creator__role','book__title')
The output is:
{'book__creators__name': 'Garczyński Stefan', 'book__creator__role': 'T', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
{'book__creators__name': 'Święcka Teresa', 'book__creator__role': 'T', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
{'book__creators__name': 'Beard Charles', 'book__creator__role': 'A', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
{'book__creators__name': 'Beard Mary', 'book__creator__role': 'A', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
by author:
book = Copy.objects.filter(book__creators__name='Beard Mary', book__creator__role='A').values('book__creators__name', 'book__creator__role', 'book__title')
the output:
{'book__creators__name': 'Beard Mary', 'book__creator__role': 'A', 'book__title': 'Rozwój cywilizacji amerykańskiej tom 1-2'}
The view and template are just a draft, it doesn't work yet. I' trying first to find a solution in the shell.
the view:
class Searchg(View):
def get(self, request, *args, **kwargs):
form = SearchForm()
return render(request, 'working/search.html', {'form': form})
def post(self, request, *args, **kwargs):
form = SearchForm(request.POST)
if form.is_valid():
author = form.cleaned_data['author']
title = form.cleaned_data['title']
if author and title:
books = Book.objects.filter(creators__name__icontains=author,
creator__role='A', title__icontains=title).order_by('title')
print('a ' , len(books))
elif author:
books = Book.objects.filter(creators__name__icontains=author, creator__role='A').order_by('title')
print('b ' , len(books))
elif title:
books = Book.objects.filter(title__icontains=title).order_by('creators__name')
print('c ' , len(books))
else:
print("Please enter search criteria")
books = books.values('creators__name', 'title').annotate(title_count=Count('title'))
paginator = Paginator(books, 25)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
cache.set('books', books)
return redirect(reverse('working:book_list'))
and template:
{% if page_obj %}
<table class="table table-bordered">
<thead>
<tr>
<th scope="col"></th>
<th scope="col"> Liczba egzemplarzy</th>
<th scope="col">Tytuł</th>
<th scope="col">Autor</th>
</tr>
</thead>
<tbody>
<tr>
{% for book in page_obj %}
<th scope="row">{{ forloop.counter0|add:page_obj.start_index}}</th>
<td>{{ book.title_count }}</td>
<td><em> {{ book.title }}</em></td>
<td> {{ book.creators__name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<button class="btn btn-light">« first</button>
<button class="btn btn-light">previous </button>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<button class="btn btn-light">next</button>
<button class="btn btn-light">last »</button>
{% endif %}

This is one of the (many) reasons why using .values(…) [Django-doc] is often not a good idea. One typically uses this to group on a special field, etc. Not to obtain values.
Furtherdmore you probably want to query on the Book model, not on the Copy model, since that will make it harder to render each book with one record. You thus can filter with:
from django.db.models import Count
books = Book.objects.filter(
creator__name='Beard Mary',
creator__role='A'
).annotate(
ncopies=Count('copy')
).prefetch_related('creator_set')
and then in the template, you can render this for example with:
{% for book in books %}
{{ book.title }}: {{ book.ncopies }} copies
{% for creator in book.creator_set.all %}
{{ creator.person.name }}: {{ creator.role }}
{% endfor %}
{% endfor %}
Here the .creator_set will not take the filtering of the QuerySet into account, since this is handled in a separated query.
You can also query on the book.copy_set.all in the template, to retrieve all related Copy objects.

Related

Django: Accessing full User information via ManyToMany field

everyone- I'm new to Django and working on my first big project and I'm having trouble with accessing default Django User model information via a ManyToMany relationship. I've spent a great deal of time searching and can't crack it.
models.py
class Event(models.Model):
event_name = models.CharField(max_length=200, null=True, unique=True)
#etc...
class School(models.Model):
user = models.ManyToManyField(User)
event = models.ForeignKey(Event, null=True, on_delete=models.PROTECT)
#etc...
My url contains the id of the Event, so...
views.py
def schools(request, pk):
event = Event.objects.get(id=pk)
school = School.objects.filter(event=event)
return render(request, 'accounts/schools.html', {'event':event, 'school':school})
template
{% for school in school %}
<tr>
<td>{{school.name}}</td>
<td>{{school.user.all}}</td>
{% endfor %}
On my template, I'm able to use {{school.user.all}} to get me a Queryset displayed with the username of each User, but I want the first_name and last_name and can't see to figure out how to get that..
Thank you for any suggestions. I greatly appreciate your time!
You should include both schools and users to your context.
You can do this with a dictionary. Add each school as a key, and users of each school as its values. Then you can pass this dictionary to your template.
View function:
def schools(request):
school_dict = dict()
schools = School.objects.all()
for school in schools:
school_dict[school] = school.user.all()
return render(request, 'accounts/schools.html', {'schools': school_dict})
And in your template:
{% for school, users in schools.items %}
<h3>{{ school.title }}</h3>
<table>
{% for user in users %}
<tr>
<td>{{ user.first_name }}</td>
<td>{{ user.last_name }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
I was able to add this to my school model to get what I wanted:
def director(self):
test = ",".join([str(p) for p in self.user.all()])
user = User.objects.get(username=test)
return user.first_name + " " + user.last_name
HOWEVER: if there is more than one user associated with "School" it displays blank

Django initial loading of page taking too long

Hi I am a beginner at Django and I am working on a project that lists 100 companies in each page along with there contacts and also the amount of items sold. Here is an example:
As you can see the initial loading time of the page is very high. But when I refresh the page it refreshes very fast because I am using caching.
Here are some of my other files:
models.py
from __future__ import unicode_literals
from django.db import models
class Company(models.Model):
name = models.CharField(max_length=150)
bic = models.CharField(max_length=150, blank=True)
def get_order_count(self):
orders = self.orders.count()
return orders
def get_order_sum(self):
orders = Order.objects.filter(company=self)
total_sum = sum([x.total for x in orders])
return total_sum
class Meta:
ordering = ['-id']
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)
email = models.EmailField()
def get_order_count(self):
orders = self.orders.count()
return orders
class Order(models.Model):
order_number = models.CharField(max_length=150)
company = models.ForeignKey(Company, related_name="orders", on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, related_name="orders", on_delete=models.SET_NULL, blank=True, null=True)
total = models.DecimalField(max_digits=18, decimal_places=9)
order_date = models.DateTimeField(null=True, blank=True)
added_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
def __str__(self):
return "%s" % self.order_number
views.py
from django.shortcuts import render
# Create your views here.
from django.views.generic import ListView
from mailer.models import Company, Contact, Order
class IndexView(ListView):
template_name = "mailer/index.html"
model = Company
paginate_by = 100
The html
<div class="container">
<table class="table table-borderless">
{% if is_paginated %}
<tr><td>
{% if page_obj.has_previous %}
«
{% endif %}
</td>
<td></td>
<td></td>
<td>
{% if page_obj.has_next %}
»
{% endif %}
</td>
</tr>
{% endif %}
<tr>
<th>Name</th>
<th>Order Count</th>
<th>Order Sum</th>
<th>Select</th>
</tr>
{% 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 %}
</table>
</div>
Is there any way in which I can reduce the initial load time. Please show me an efficient way to solve this problem.
Each {{company.get_order_count}} will hit the DB. Admittedly with a very simple query, but even so, it will slow things down.
You want to annotate the objects with this count. Use
from django.db.models import Count
class IndexView(ListView):
template_name = "mailer/index.html"
model = Company
paginate_by = 100
def get_queryset(self):
return super().get_queryset().annotate( num_orders=Count('orders') )
and replace {{ company.get_order_count }} with {{ company.num_orders }}. This will turn N+1 DB queries into one DB query.
That's the easy one. There's a similar problem with get_order_sum which can almost certainly be solved with another annotation involving the django.db.Sum. Sorry but its late and my stomach is growling and I don't have any confidence that I would get that one right straight off the top of my head.
The cheat sheet on annotation is here. You might also need to look at aggregation.
Oh, and install Django_debug-toolbar in your developer environment. Every time in future it gets slow, you can just click there to see what SQL was executed and how long it took.

how to display many to many field properly in django

This code saves the data and displays the data as i wanted.but while displaying courses it displays like this , ]> .It displays with
QuerySet[].i only want the courses name to be displayed.How can i do that
models.py
class Teacher(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=50)
phone = models.CharField(max_length=15)
email = models.EmailField()
image = models.ImageField(upload_to='Teachers',blank=True)
courses = models.ManyToManyField(Course)
views.py
def viewteacher(request):
teachers = Teacher.objects.all().order_by('-joined_date')
year1 = datetime.datetime.today().year
return render(request,'students/view_teacher.html',{'teachers':teachers,'year_list':range(2016,year1+1)})
template
{% for teacher in teachers %}
<tr>
<td>{{teacher.name}}</div></td>
<td>{{teacher.courses.all}}</td>
<td>{{teacher.address}}</td>
<td>{{teacher.phone}}</td>
<td>Profile</td>
<td>Edit</td>
<td>Delete</td>
</tr>
{% endfor %}
</tbody>
A QuerySet is rendered like <QuerySet [ ... ]>, so you can iterate over the queryset, and render it properly, like:
{% for teacher in teachers %}
<tr>
<td>{{teacher.name}}</div></td>
<td>{% for course in teacher.courses %} {{course.name}} {% endfor %}</td>
<td>{{teacher.address}}</td>
<td>{{teacher.phone}}</td>
<td>Profile</td>
<td>Edit</td>
<td>Delete</td>
</tr>
{% endfor %}
Obtain the list of courses for every individual teacher is however not very efficient. You can use .prefetch_related to speed up the process. It will then perform a limited number of queries to fetch all the courses, and link these properly to the correct teacher(s):
def viewteacher(request):
teachers = Teacher.objects.prefetch_related('courses').order_by('-joined_date')
year1 = datetime.datetime.today().year
return render(
request,
'students/view_teacher.html',
{'teachers':teachers,'year_list':range(2016,year1+1)}
)

How to get a value from related object in a django template

I'm quite new with python and django and I apologize if the topic was already covered, but I coudln't find an answer to my question.
I have theese classes in my models.py
class Category(models.Model):
category_type = models.CharField(max_length=50)
description = models.TextField()
def __unicode__(self):
return self.category_type
class Area(models.Model):
area_type = models.CharField(max_length=50)
description = models.TextField()
def __unicode__(self):
return self.area_type
class Topic(models.Model):
topic_type = models.CharField(max_length=50)
description = models.TextField()
def __unicode__(self):
return self.topic_type
class Tag(models.Model):
tag_type = models.CharField(max_length=50)
description = models.TextField()
def __unicode__(self):
return self.tag_type
class GenericRecord(models.Model):
document_title = models.CharField(max_length=500)
category = models.ForeignKey("Category")
area = models.ForeignKey("Area")
topic = models.ForeignKey("Topic")
tag = models.ForeignKey("Tag")
note = models.TextField(null=True, blank=True)
link = models.TextField(null=True, blank=True)
file_upload = models.FileField(upload_to='GenericRecord/', null=True, blank=True)
class Meta:
abstract = True
class Document(GenericRecord):
code = models.CharField(max_length=500, null=True, blank=True)
reference = models.CharField(max_length=500)
issue_date = models.DateField(auto_now=False, auto_now_add=False)
validation_date = models.DateField(auto_now=False, auto_now_add=False, null=True, blank=True)
def get_admin_url(self):
return reverse("admin:%s_%s_change" % (self._meta.app_label, self._meta.model_name), args=(self.id,))
def __unicode__(self):
if self.code:
return "%s-%s" % (self.code, self.document_title)
else:
return "--%s" % self.document_title
And this piece of code in views.py
def documents_list(request):
# Generate counts of some of the main objects
num_docs=Document.objects.all().count()
docs=Document.objects.all()
num_articles=Article.objects.all().count()
articles=Article.objects.all()
template='documents_management.html'
for object in docs:
object.fields = dict((field.name, field.value_to_string(object)) for field in object._meta.fields)
# Render the HTML template documents_management.html with the data in the context variable
return render(request,template, context={'docs':docs, 'num_docs':num_docs,'docs':docs, 'num_articles':num_articles, 'articles':articles})
In the template I'm trying to get a table with all the values, but for the related objects I get the primary key (of course).
Here is the code in my template:
<table class="w3-table-all">
{% for object in docs %}
{%if forloop.first %}
<tr>
{% for field, value in object.fields.iteritems %}
<th>{{ field }}</th>
{% endfor %}
</tr>
{%endif%}
{% endfor %}
{% for object in docs %}
{% for field, value in object.fields.iteritems %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
What I see in my browser
My question is, how can I get the object Category, Area etc... in order to get the category_type, area_type etc. value?
Thanks!
Here is an great example from the docs: https://docs.djangoproject.com/en/1.11/intro/tutorial03/#use-the-template-system
What you are searching for is the question.choice_set.all part.
UPDATE due to a hint of bad style
As mentioned by daniel you should ditch the Field.to_value_string method.
Since I am not a fan of implicit code I always recommend to code templates as explicit as possible, here would by my version of your template
<table class="w3-table-all">
{% for doc in docs %}
{% if forloop.first %}
<tr>
<th>Document Title</th>
<th>Category Type</th>
<th>Area Type</th>
<th>...</th>
</tr>
{% else %}
<tr>
<td>{{ doc.document_title }}</td>
<td>{{ doc.category.category_type }}</td>
<td>{{ doc.area.area_type }}</td>
<td>...</td>
</tr>
{% endif %}
{% endfor %}
</table>
What I changed:
only one for loop, you started with the if forloop.first you might also finish with the else case
refactored object to doc because objects is used often within django for model managers
add the fields explicit doc.area.area_type, this will prevent a new field in the model to also appear in the template but here I recommend an explicit over an implicit coding style
Also you can remove this from document_list:
for object in docs:
object.fields = dict((field.name, field.value_to_string(object)) for field in object._meta.fields)
The problem is in your use of Field.value_to_string. As the docstring on that method shows, this is for serialization, not for displaying values.
A much simpler and more effective way of doing this would be to use getattr, which gets the actual value; the template will then take care of converting those to a string, which in the case of the foreign keys will call the __unicode__ method of the related objects.
object.fields = dict((field.name, getattr(obj, field.name)) for field in object._meta.fields)

pre-populating partial Initial data in a Django formset

I’m having difficulty implementing initial data in my django formset.
For context, I’m building an attendance app where a list of students exist and need to be assessed for attendance every day.
What I’m trying to do is have an administrator click on a link which has a date listed on it. They will then be taken to data grid where each row represents the number of students in the system along with 4 columns (student name, date, present/absent drop down, a notes field). The goal is to have the student name field be pre-populated with the the list of students in the Student model, the date field be pre-populated with the date on the link the user clicked and the attendance and notes fields be user inputs.
Any help would be much appreciated
Thanks!
—
Student model
class Student(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
GENDER_CHOICES = (
('male', 'male'),
('female', 'female'),
)
gender = models.CharField(max_length=12, choices=GENDER_CHOICES, null=False, blank=False)
date_of_birth = models.DateField(auto_now=False, auto_now_add=False)
#property
def full_name(self):
return ''.join([self.first_name, '_', self.last_name])
def __unicode__(self):
return self.full_name
Attendance model
class Attendance(models.Model):
student = models.ForeignKey('students.Student')
attendance_date = models.DateField(auto_now=False, auto_now_add=False)
STATUS_CHOICES = (
('present', ‘1’),
('absent', ‘0’),
)
status = models.CharField(max_length=12, choices=STATUS_CHOICES, null=False, blank=False)
notes = models.CharField(max_length=300, null=True, blank=True)
class Meta:
unique_together = ("student", "attendance_date")
def __unicode__(self):
return self.student
Attendance form
class AttendanceForm(forms.ModelForm):
class Meta:
model = Attendance
fields = ["student","attendance_date", "status", "notes"]
View
def add_attendance(request):
s = Student.objects.all().values()
AttendanceFormSet = formset_factory(AttendanceForm, extra=0)
formset = AttendanceFormSet(request.POST or None, initial=s)
if formset.is_valid():
try:
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect('/')
except:
return HttpResponseRedirect('/')
context = {
"formset": formset,
}
return render(request, "group_attendance.html", context)
Template
<table id="formset" class="form">
{{ formset.management_form }}
{% for form in formset.forms %}
{% if forloop.first %}
<thead><tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr></thead>
{% endif %}
<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 %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
I'm not really sure, if I understood your question the intended way. I think you're looking for a way to generate dynamic forms, which were pre-filled with data from the db.
A really helpful article about that, can be found here https://jacobian.org/writing/dynamic-form-generation/
You don't have a fixed numbers of students (-> fields in form), so you have to generate as many fields as needed. So you have to iterate over the students and create a form field for every single one.
You can find this in the article above. Just down below is a code snippet and an explanation of it:
class UserCreationForm(forms.Form):
username = forms.CharField(max_length=30)
password1 = forms.CharField(widget=forms.PasswordInput)
password2 = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
extra = kwargs.pop('extra')
super(UserCreationForm, self).__init__(*args, **kwargs)
for i, question in enumerate(extra):
self.fields['custom_%s' % i] = forms.CharField(label=question)