How to get multiple values from joined tables - django

How can I get all the laws from all the courses that an specific user is subscribed?
class Law(models.Model):
name = models.CharField('Nome', max_length=100)
slug = models.SlugField('Atalho')
description = models.TextField('Description', blank = True, null=True)
class Course(models.Model):
name = models.CharField('Nome', max_length=100)
slug = models.SlugField('Atalho')
description = models.TextField('Descrição', blank = True, null=True)
laws = models.ManyToManyField(Law, related_name='law_course')
class Subscription(models.Model):
STATUS_CHOICES=(
(
(0, 'Pendente'),
(1, 'Approved'),
(2, 'Canceled'),
)
)
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE, verbose_name="Usuário", related_name="inscricao") #inscricao é criado no usuario
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name="Course", related_name="subscription")
status = models.IntegerField(
'Status', choices=STATUS_CHOICES, default = 0, blank=True
)
class Meta:
unique_together = (('user','course'))
So, the user can be subscribed in one or more courses. Each course has one or many laws.
I have a page that I need to list all the laws of each course that an specific user is subscribed if the subscription is Approved (status = 1).
Example:
The user John has subscribed in 2 courses: Course A and Course B.
Course A laws: law1, law2, law3
Course B laws: law1, law20, law30
So, when John is logged in, I need to show the laws of the courses and the name of the course:
law1 - Course A, Course B
law2 - Course A
law3 - Course A
law20 - Course B
law30 - Course B
Note that I can't show law 1 twice, but I showed each course that contains this law.
I did my best to be clear. Tks in advance

You can use a set to store the laws (which will eventually be unique).
laws_set = set()
subscriptions = Subscription.objects.filter(user=your_user, status=1)
For each subscription, get the course and then get all the laws corresponding to that course.
for subscription in subscriptions:
laws = subscription.course.laws.all()
for law in laws:
laws_set.add(law)
Now for each law in the laws_set, you can print the corresponding courses.
for law in laws_set:
print(law.name, end='')
for course in law.law_course.all(): # Since law_course is the related name
print(course.name, end='')
print('\n')

Related

How to apply lookups in django filter query according to conditions?

I want to make a query where i can apply lookups only when some conditions satisfy.
e.g
A customer can pick only that food from stall for which he paid for
class Customer(models.Model):
food_type = models.CharField()
fruit_id = models.ForeignKey(Fruit, null=True)
vegetable_id = models.ForeignKey(Vegetable, null=True)
is_paid = models.BooleanField()
class Food(models.Model):
fruit_id = models.ForeignKey(Fruit, null=True)
vegetable_id = models.ForeignKey(Vegetable, null=True)
So i need to do something like:
q = Food.objects.all()
if Customer.objects.filter(id=(id of customer), food_type='fruit', is_paid=True).exists():
q = q.filter(fruit_id__in=Customer.objects.filter(id=(id of customer), food_type='fruit', is_paid=True).values_list('fruit_id', flat=True))
if Customer.objects.filter(id=(id of customer), food_type='vegetable', is_paid=True).exists():
q = q.filter(vegetable_id__in=Customer.objects.filter(id=(id of customer), food_type='vegetable', is_paid=True).values_list('vegetable_id', flat=True))
How can i optimize this as this is a small example, in my case there are many more conditions. I want to reduce the number of times it is hitting the database.
Also is there any way i can use conditional expressions here? e.g When()
Any help will be appreciated.
You can use Q.
from django.db.models import Q
q = Food.objects.all()
q = q.filter(
Q(
vegetable_id__in=Customer.objects.filter(id=(id of customer), food_type='vegetable', is_paid=True).values_list('vegetable_id', flat=True)
)|Q(
fruit_id__in=Customer.objects.filter(id=(id of customer), food_type='fruit', is_paid=True).values_list('fruit_id', flat=True)
)
)
The reason for the complexity is because of the structure of your models - you have the Customer connected to Fruit and/or Vegetables and Food also connected to Fruit/Vegetables, so Customer and Food only potentially connect through Fruit/Vegetable - very confusing and long-winded.
It also explains why you have 'food_type' on your Customer model, to try and compensate. I would suggest a much simpler structure but with one additional model:
class Customer(models.Model):
food = models.ManyToManyField(Food, ... # customer can have many Food
is_paid = models.BooleanField()
# create a new food type model that stores Fruit, Vegetable, etc.
class FoodType(models.Model):
name = models.CharField(...
class Food(models.Model):
# food now connects to the food type
food_type = models.ForeignKey(FoodType, ...
You can now keep your queries far more concise:
# get food that the customer has paid for
Food.objects.filter(customer__is_paid=True)
# get fruit that the customer has paid for
Food.objects.filter(customer__is_paid=True, food_type__name="Fruit")

DJANGO - how can I filter an object filtering by another object filtered

I'm trying to make an advanced filter with django objects but I just have no clue of how to do it.
I'll try to explain myself:
I have 2 objects:
Consumptions:
class Consumption(LogsMixin, models.Model):
"""Definición del modelo de Consumos"""
STATES = [
('not-prevalidated', 'No prevalidado'),
('prevalidated', 'Prevalidado'),
('pendant', 'Pendiente validación cliente'),
('accepted', 'Aceptado'),
]
client = models.ForeignKey('authentication.Client', verbose_name=("Cliente"), null=True, default=None, on_delete=models.SET_DEFAULT)
access_date = models.DateField("Fecha de acceso", auto_now=False, auto_now_add=False)
status = models.CharField("Estado", max_length=20, choices=STATES, null=False, blank=False)
and Clients
class Client(LogsMixin, models.Model):
"""Model definition for Client."""
company_name = models.CharField("Nombre de la empresa", max_length=150, default="Nombre de empresa", null=False, blank=False)
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
dateadded = models.DateTimeField("Fecha de inserción", default=datetime.datetime.now)
And now I want to count all clients that has some consumption in 'pendant' state and in certain date.
As you can see consumptions has one client related.
I've checked the docs https://docs.djangoproject.com/en/3.1/topics/db/queries/ but I just can't get what I want.
Could someone help me? :(
What ended up fixing OP's problem was a distinct() call to avoid any duplicate rows from being selected. Fair reminder though that in most cases, a call for distinct() isn't required. It's just that when queries span over multiple tables there's a chance for a row to be selected twice.
[read comments]
client_count = Client.objects.filter(
consumption__status="pendant",
consumption__access_date="2020-11-03").distinct().count()
When you say "I want to count the clients that has consumptions in certain state" what exactly do you mean? Can you provide more detail as to what exactly you are expecting?
The other answer with the following code will indeed count the number of clients that have consumptions in a certain state.
client_count = Client.objects.filter(
consumption__status="pendant",
consumption__access_date="2020-11-03").count()
But it will not count each client twice if the client has multiple 'consumptions', is that what you wish? If that's the case then perhaps filtering the consumptions is a better idea?
count = Consumption.objects.filter(
status="pendant",
access_date="2020-11-03").count()
This will give you a count of the total consumptions in that state, and if you wish to access all the clients then simply access consumption.client.
lookup through the span relation as,
client_count = Client.objects.filter(
consumption__status="pendant",
consumption__access_date="2020-11-03").count()
you can try this way:
from django.db.models import Count
client_count = Consumption.objects.filter(status='pendant',access_date=some_date).aggregate(client_count = Count('client'))['client_count']
Could this do the job:
nb_clients = Client.objects.filter(consumption_set__status='pendant',consumption_set__access_date__range=(start_date, end_date)).count()
doc backward relationship : https://docs.djangoproject.com/en/3.1/topics/db/queries/#following-relationships-backward
doc range : https://docs.djangoproject.com/en/3.1/ref/models/querysets/#range

django.db.utils.IntegrityError, invalid foreign key. Can someone help me and teach me what happens?

can someone help me and teach me what happens?
For me not to make mistakes again?
I want to bind a foreign key (class category) to my course class. And I did something that doesn't allow me to get out of it.
Error: django.db.utils.IntegrityError: The row in table 'courses_course'
with primary key '3' has an invalid foreign key:
courses_course.category_id contains a value 'outros' that does not have a
corresponding value in courses_category.id.
CATEGORY = [('eng','ENGENHARIA'),('prog','PROGRAMAÇÃO'),('hum','HUMANAS'),('saude','SAÚDE'),
('outros','OUTROS')] // i'm from brazil
class Category(models.Model):
title_category = models.CharField('Nome da Categoria', max_length= 63 , choices = CATEGORY )
class Course(models.Model):
title = models.CharField('Nome', max_length= 100)
slug = models.SlugField('Atalho',max_length=50)
description = models.TextField('Descricao',blank = True)
follows = models.IntegerField(default = 0) //allows negative numbers, I know. I'll fix it ...
imagem = models.ImageField(upload_to = 'courses/images',verbose_name= 'Imagem',
null=True, blank = True)
category = models.ForeignKey('Category', on_delete = models.CASCADE)
created_at = models.DateTimeField('Criado em',auto_now_add = True )
updated_at= models.DateTimeField( 'Atualizado em', auto_now = True )
Does anyone imagine another structure or class that allows you to create educational courses / videos that have categories? Or am I on the right track for a beginner?
The only thing I can guess with the information you gave (you didn't say WHEN you get this error) is that you tried to do something such as :
my_course.category = 'outros'
my_course.save()
Which is incorrect since it isn't a Category object. A ForeignKey field must refer to an object of type to which you point to.
category = Category.objects.create(title_category='outros')
my_course.category = category
my_course.save()
This would work since you give a reference to a Category object in your Course object.
Do not hesitate to read the documentation about ForeignKey fields again.

How to get number of items grouped by a property of an intermediate model

I would like to have something like this
Adventure (4) | Sci-fi (12)
which are the books, in a bookshop, linked by a local price.
Say, Hobbit is $5 at Amazon and $6 at Barnes. So if I was listing the books in Amazon I will have Adventure (1) as the count of the books with a specified price in amazon.
If I do like this I get the correct Genres:
for u in Bookshop.objects.get(pk=1).BookBookshopLink_set.all():
print u.book.genre
which would print, e.g.:
Sci-fi
Sci-fi
Adventure
Here are the models:
from parler.models import TranslatableModel, TranslatedFields
from parler.managers import TranslationManager
class Genre(TranslatableModel):
translations = TranslatedFields(
name=models.CharField(max_length=200),
slug=models.SlugField(),
description=models.TextField(blank=True),
meta={'unique_together': [('language_code', 'slug')]},
)
published = models.BooleanField(default=False)
class Book(TranslatableModel):
translations = TranslatedFields(
name=models.CharField(max_length=200),
slug=models.SlugField(),
description=models.TextField(blank=True),
meta={'unique_together': [('language_code', 'slug')]},
)
genre = models.ForeignKey(Genre, blank=True, null=True)
published = models.BooleanField(default=False)
class Bookshop(TranslatableModel):
translations = TranslatedFields(
name=models.CharField(max_length=200),
description=models.TextField(_('Description'), default='', blank=True),
slug=models.SlugField(),
meta={'unique_together': [('slug', 'language_code')]},
)
booklist = models.ManyToManyField(Book, blank=True, through='BookBookshopLink')
class BookBookshopLink(TranslatableModel):
bookshop = models.ForeignKey(Bookshop)
book = models.ForeignKey(Book)
price = models.IntegerField(blank=True, null=True)
To do what you're trying to achieve in one query, you need to use Count, annotate and values_list
I'll show you a code example and then I'll try to explain it:
from django.db.models import Count
from your_project.models import *
Genre.objects.all().values_list('name').annotate(num_books=Count('book'))
.values_list('name'): This return a list of all genres by name
.annotate(num_books=Count('book')): This count books for each Genre
I have a similar models structure in my projects and when I execute that code, I get this as answer:
[(u'GENRE_NAME', 13), (u'GENRE_NAME', 14), (u'GENRE_NAME', 0),...]
You can parse the output of this query to fit your expectations
I also recomend you to check oficial documentation Django Agreggation
This loops over all your genres and prints out how many of them there are.
Even if there are 0 in a genre.
for a_genre in Gendre.objects.all():
print Book.objects.filter(genre=a_genre).count()
and if you want it printed with the genre aswel
for a_genre in Gendre.objects.all():
print "%s (%d)" % (a_genre, Book.objects.filter(genre=a_genre).count())
Documentation for filters in django : https://docs.djangoproject.com/en/1.7/topics/db/queries/#retrieving-specific-objects-with-filters

ManyToMany relations and Intermediate models (newbie guidance)

I'd really appreciate some guidance on how to construct the following models. I want to make sure I'm doing this in the ideal way.
I want to keep track of some info about employees at my work. Basically, past Education and past Work Experience
So here's the relations I can think of
Education
An employee could been to several school and each school can have many students (m2m)
Students attend a school for a period of time
a student can have multiple degrees form the same school and that school offers multiple degrees (m2m)
Work (almost the same as education)
m2m relation with employees and companies
employees work at a company to a set time
employes could have several jobs at one company (m2m)
Following basically what's above, here's the code I've worked out:
#make a list of numbers in a tuple
START_DATE_CHOICES = dict((str(x),str(x)) for x in range(date.today().year-30,date.today().year+1))
END_DATE_CHOICES = START_DATE_CHOICES
START_DATE_CHOICES = START_DATE_CHOICES.items()
START_DATE_CHOICES.sort()
START_DATE_CHOICES.reverse()
START_DATE_CHOICES = tuple(START_DATE_CHOICES)
END_DATE_CHOICES['current'] = 'current'
END_DATE_CHOICES = END_DATE_CHOICES.items()
END_DATE_CHOICES.sort()
END_DATE_CHOICES.reverse()
END_DATE_CHOICES = tuple(END_DATE_CHOICES)
#both schools and companies
class Institution(models.Model):
name = models.CharField(max_length = 75)
class Education(models.Model):
DEGREE_CHOICES = (
('A.A.', 'Associate'),
('Minor', 'Minor'),
('B.A.', 'Bachelor of Arts'),
('B.S.', 'Bachelor of Science'),
('Masters', 'Masters'),
('Ph. D.', 'Doctor of Philosophy'),
)
school = models.ManyToManyField(Institution, through='Dates')
degree = models.CharField(max_length = 5, choices = DEGREE_CHOICES, null = True)
subject = models.CharField(max_length = 20, null = True)
class Work(models.Model):
company = models.ManyToManyField(Institution, through='Dates')
position = models.CharField(max_length = 50, null = True)
jobDescription = models.TextField(null = True)
class Dates(models.Model):
education = models.ForeignKey(Education, null = True)
work = models.ForeignKey(Work, null = True)
institution = models.ForeignKey(Institution)
start = models.CharField(max_length = 4, choices = START_DATE_CHOICES)
end = models.CharField(max_length = 7, choices = END_DATE_CHOICES)
class Person(models.Model):
....
school = models.ManyToManyField(Institution, blank=True)
education = models.ManyToManyField(Education, blank = True)
company = models.ManyToManyField(Institution, blank = True, related_name="%(app_label)s_%(class)s_related")
work = models.ManyToManyField(Work, blank=True)
....
So my question is: Is this an acceptable way of doing it? I'm a newbie and I'm not sure if I'm just abusing relationships
also, I'm sure there's a easier way to do the whole start/end date thing... wasn't able to figure it out though
Any pointers would be greatly appreciated, Thank you!
For many to many fields you should use the plural form. for Person that would be educations and jobs for instance.
Also it is convention to write job_description instead of jobDescription in model fields.
In Person the fields school and company are redundant because that information is accessible through educations (currently education) and jobs (currently work) respectively.
I think you don't need the many to many field company in Work. A simple ForeignKey should suffice. I think you only have a specific job at ONE company for a given time. You can have mutliple jobs at the same time at multiple companies but still every job is at a single employer. Or am I wrong?
The same goes for Education. You only have a degree in a specific subject from one school even if you went to several schools in the course of this education. Or do you want to model that situation that accurately?
The naming of all dates is a bit misleading since they all are acually years. You also could use NULL in the end year as 'current' and use PositiveIntegerFields.
Now I would have this code:
#both schools and companies
class Institution(models.Model):
name = models.CharField(max_length = 75)
class Education(models.Model):
DEGREE_CHOICES = (
('A.A.', 'Associate'),
('Minor', 'Minor'),
('B.A.', 'Bachelor of Arts'),
('B.S.', 'Bachelor of Science'),
('Masters', 'Masters'),
('Ph. D.', 'Doctor of Philosophy'),
)
school = models.ForeignKey(Institution)
degree = models.CharField(max_length = 5, choices = DEGREE_CHOICES, null = True)
subject = models.CharField(max_length = 20, null = True)
start_year = models.PositiveIntegerField()
end_year = models.PositiveIntegerField(null=True)
class Job(models.Model):
company = models.ForeignKey(Institution)
position = models.CharField(max_length = 50, null = True)
job_description = models.TextField(null = True)
start_year = models.PositiveIntegerField()
end_year = models.PositiveIntegerField(null=True)
class Person(models.Model):
....
educations = models.ManyToManyField(Education, blank = True)
jobs = models.ManyToManyField(Work, blank=True)
....
Of course if you want to have choices for the years you can have them
YEAR_CHOICES = ((year, str(year)) for year in range(...))
Alternatively you could write a custom validator for your year fields.