Django fetch manytomany values as list - django

class Groups:
name = models.CharField(max_length=100)
class User:
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
group = models.ManyToManyField(Groups)
i need user details with groupname as list not as separate records.
User.objects.values('firstname','lastname', 'group__name')
While i'm querying like above, i'm getting like this
<QuerySet [{'firstname': 'Market', 'lastname': 'Cloud', 'group__name':
'Group 5'}, {'firstname': 'Market', 'lastname': 'Cloud',
'group__name': 'Group 4'}]>
but i want like this
<QuerySet [{'firstname': 'Market', 'lastname': 'Cloud', 'group__name':
['Group 5', 'Group 4']}]>
is there a way, i can query like this without doing separate query.

If you're using postgres you can use ARRAY_AGG function. In Django ORM like below:
from django.contrib.postgres.aggregates import ArrayAgg
User.objects \
.annotate(list_of_group_names=ArrayAgg('group__name')) \
.order_by('id').distinct() \
.values('firstname', 'lastname', 'list_of_group_names')
Note: distinct is useful, because joining tables can result in duplicates

Related

django reverse foreign key pefetch related, queryset condition not working

Consider the following condition:
class Book(models.Model):
name = models.CharField(max_length=300)
price = models.IntegerField(default=0)
def __str__(self):
return self.name
class Store(models.Model):
name = models.CharField(max_length=300)
default = models.BooleanField(default=False)
books = models.ForeignKey(Book, on_delete=models.CASCADE)
So, for this query:
Book.objects.prefetch_related(Prefetch('store_set',
queryset=Store.objects.filter(default=False)))
.values("store__name", "name", "store__default")
The SQL query is not considering queryset default=True condition
SELECT "core_store"."name",
"core_book"."name",
"core_store"."default"
FROM "core_book"
LEFT OUTER JOIN "core_store"
ON ("core_book"."id" = "core_store"."books_id")
Result:
<QuerySet [{'store__name': 'Subway Store', 'name': 'Hello', 'store__default': False},
{'store__name': 'Times Square', 'name': 'Hello', 'store__default': False},
{'store__name': 'Subway Store', 'name': 'GoodBye', 'store__default': True},
{'store__name': 'Times Square', 'name': 'GoodBye', 'store__default': False},
{'store__name': 'Subway Store', 'name': 'Greetings', 'store__default': True},
{'store__name': 'Subway Store', 'name': 'HateWords', 'store__default': False}]>
I want to have a query set condition while prefetching the query. I am not able to find any way to do it in one query or a minimum number of queries.
I was thinking it should make a where condition with the OUTER JOIN with core_store table. Here
LEFT OUTER JOIN "core_store"
ON ("core_book"."id" = "core_store"."books_id")
If you want only the values as stated in your question then you can directly use fk relationship from the Store model to Book model as below. No need to do reverse lookup.
Store.objects.filter(default=True).values('name', 'default', 'books__name')
But if u want to fetch all Books and also their corresponding Store then you can use only on prefetch_related. .values does not work with prefetch_related as stated in the docs. You can also use a for loop after assigning a to_attr in your prefetch_related query. The for loop does not do a db query. Instead it gets the value from prefetched results.
books_stores = Book.objects.prefetch_related(Prefetch('store_set',
queryset=Store.objects.filter(default=False),
to_attr='stores'))
for book_store in book_stores:
stores = book_store.stores

Convert raw sql query to django orm

I written this query in PostgreSQL and I'm confused of conversion of this query to django orm
SELECT count(*),
concat(date_part('month', issue_date), '/', date_part('year', issue_date) ) as date
FROM affiliates_issuelog
WHERE tenant_id = '{tenant_id}'
GROUP BY date_part('month', issue_date),
date_part('year', issue_date)
ORDER BY date_part('year', issue_date) desc,
date_part('month', issue_date) desc
I have this model that records the insertion of new affiliates by date and by institution (tenant), only I need to receive from the query the total amount of records inserted per month in the year, and I was using the listview to make my pages until then but I don't know how to filter this data using orm.
class IssueLog():
tenant = models.ForeignKey("tenants.Federation", on_delete=models.CASCADE)
issue_date = models.DateField(
default=date.today, verbose_name=_("date of issue")
)
class Meta:
verbose_name = _("Entrada de emissão")
verbose_name_plural = _("Entradas de emissão")
def __str__(self):
return f"{self.institution}, {self.tenant}"
My pages that return a list of data I did as the example below, is it possible to pass the data as I want through get_queryset()?, I already managed to solve my problem using the raw query, but the project is being done only with orm so I wanted to keep that pattern for the sake of the team. Ex:
class AffiliateExpiredListView(HasRoleMixin, AffiliateFilterMxin, ListView):
allowed_roles = "federation"
model = Affiliate
ordering = "-created_at"
template_name_suffix = "issued_list_expired"
paginate_by = 20
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["renew_form"] = AffiliateRenewForm()
tenant_t = self.request.user.tenant
context["cancel_presets"] = tenant_t.cancelationreason_set.all()
return context
def get_queryset(self):
return super().get_queryset().filter(is_expired=True).order_by('full_name')
You can query with:
from django.db.models import Count
from django.db.models.functions import ExtractMonth, ExtractYear
IssueLog.objects.values(
year=ExtractYear('issue_date'),
month=ExtractMonth('issue_date')
).annotate(
total=Count('pk')
).order_by('-year', '-month')
This will make a queryset with dictionaries that look like:
<QuerySet [
{'year': 2022, 'month': 2, 'total': 14},
{'year': 2022, 'month': 1, 'total': 25},
{'year': 2021, 'month': 12, 'total': 13}
]>
I would not do string formatting in the database query, but just do this in the template, etc.
But the model can not be abstract = True [Django-doc]: that means that there is no table, and that it is only used for inheritance purposes to implement logic and reuse it somewhere else.

Accessing one model from within another in Django many-to-one using ForeignKey

Lets imagine we have two models (many-to-one model).
Code below shows that a reporter can have multiple articles
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField(null=True)
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE, null=True)
def __str__(self):
return self.headline
Let's see what I have in my database on this model.
# Reporter.objects.all().values()
# <QuerySet [
# {'id': 1, 'first_name': 'John', 'last_name': 'Smith', 'email': 'john#example.com'},
# {'id': 2, 'first_name': 'Paul', 'last_name': 'Jones', 'email': 'paul#example.com'}
# ]>
# Article.objects.all().values()
# <QuerySet [
# {'id': 5, 'headline': "1st headline", 'pub_date': datetime.date(2005, 7, 29),
# 'reporter_id': 1},
# {'id': 6, 'headline': "2nd headline", 'pub_date': datetime.date(2006, 1, 17),
# 'reporter_id': 2},
# {'id': 7, 'headline': '3rd headline', 'pub_date': datetime.date(2005, 7, 27),
# 'reporter_id': 1}
# ]>
The first reporter has two publications and second has the only.
I need to get the list of all articles for each reporter.
I tried this way (according to django docs):
Article.objects.filter(reporter__first_name='John')
It's okay. It works. I also tried to instantiate the first reporter as 'r1' and then do this:
r1.article_set.all()
And this piece of code works too.
But as I'm new to django, I think that instantiating the first reporter as 'r1' and then making a query is a bit slow. It is because django makes me run r1.save() and then r1.article_set.all(). It looks like django makes 2 query into database (first query - to save an instance, the second query to run r1.article_set.all)
Is my point of view correct? And how to query all the reporter's articles as fast as Article.objects.filter(reporter__first_name='John') but using the Reporter object?
Thanks
I also tried to instantiate the first reporter as 'r1' and then do this:
r1.article_set.all()
And this piece of code works too. But as I'm new to django, I think that instantiating the first reporter as 'r1' and then making a query is a bit slow.
Yes, but Django can load the related articles all with a second query in bulk. We do this with .prefetch_related(…) [Django-doc]:
reporters = Reporter.objects.prefetch_related('article_set')
for reporter in reporters:
print(reporter.first_name)
print(reporter.article_set.all())
Instead of the N+1 queries that your implementations make (one query to fetch all reporters, and one query per reporter to fetch the related articles), this will make two queries: one to fetch all the reporters, and one to fetch all the articles related to one of these reporters. Django will then do some joining such that the articles related to the first reporter r1 end up in r1.article_set.all()

Django filter that also grabs a reverse ForeignKey, or better way to combine associated database info?

I am working on some backend django work that requires me to grab an Employee by filtering, but I also need to grab the EmployeeAddress object that is associated to the Employee. I was wondering if this was possible within a single query. I need the employees address, and employee info to be in a combined single dictionary, to access on the front end side with JS.
I have models as such,
Class Employee(models.model):
first_name
last_name
email
Class EmployeeAddress(models.model):
employee = models.ForeignKey(Employee):
street
city
state
I have a view, that kinda does the job, but having trouble merging merging the two separate QuerySets into a single listed dictionary with all values.
I was hoping there was just a way to get the EmployeeAddress without even writing the second query, and just grabbing that associated data in the first employee_list query?
def employee_ajax_list(request):
email = request.GET.get('email', None)
employee_list = Employee.objects.filter(email=email)
employee_address = EmployeeAddress.objects.filter(employee_id__in=employee_list).values(
'street', 'city', 'state',)
# this chain kinda works, but splits them into 2 separate dictionaries?
employee_info = list(chain(employee_list.values(), employee_address))
data = {
'employee_list': employee_info
}
return JsonResponse(data)
Just looking on some advice to make this work a little smoother!
Change this line
employee = models.ForeignKey(Employee)
to
employee = models.ForeignKey(Employee, related_name="address")
That will let you access an employee's address by doing employee.address in regular code or employee__address in queries.
For example:
Employee.objects
.filter(email=email)
.exclude(address=None)
.values(
'email',
'name',
'address__street',
'address__city',
'address__state'
)
(The .exclude() clause is in case someone doesn't have an address set.)
That should output something like:
<QuerySet [{'email': 'johnsmith#example.com',
'name': 'John Smith', 'address__street':
'123 Maple St', 'address__city': 'Springfield',
'address__state': 'MO'}]>
Maybe something like this should be a bit better and in a single query:
employee_info = EmployeeAddress.objects.filter(employee__email=email).values("employee__email", "employee__<another_field>", "street", "city", "state")
data = {
'employee_list': employee_info
}
A best way of doing it should be with DRF serializers:
class EmployeeAddressSerializer(serializers.ModelSerializer):
class Meta:
fields = "__all__"
model = Employee
class EmployeeSerializer(serializers.ModelSerializer):
# you should add related_name="addresses" in the
# foreignkey if you want this works.
addresses = EmployeeAddressSerializer(many=True)
class Meta:
fields = "__all__"
model = Employee
Then to use:
employee = Employee.objects.filter(email=email).last()
return JsonResponse(EmployeeSerializer(employee).data)

Django aggregation: Group and sum over a date range

I am fetching some user stats daily and recording them in a model as follows (irrelevant parts are stripped off for simplicity):
class User(models.Model):
group = models.CharField(
choices=(('A', 'Group A'), ('B', 'Group B'),
))
class Stats(models.Model):
day = models.DateField()
user = models.ForeignKey(User)
follower_count = models.PositiveIntegerField()
As seen above, each user belongs to a group.
How can I get the sum each user's follower_counts for each group over a date range?
In other words, what is the best way to build a data structure as follows using these models? Is it possible to do it with a single aggregate query?
[{
'date': '2015-07-15',
'Group A': 26, # sum of followers of users in Group A on 2015-07-15
'Group B': 15,
}, {
'date': '2015-07-16',
'Group A': 30,
'Group B': 18,
}, {
'date': '2015-07-17',
'Group A': 32,
'Group B': 25,
}]
Thank you.
You should be able to get the desired aggregate query by using this block of code.
Stats.objects.values('day').annotate(
group_a=Sum(Case(When(user__group='A', then='follower_count'))),
group_b=Sum(Case(When(user__group='B', then='follower_count')))
)
Basically it tells the Django ORM to get the sums of the follower_count's of the two groups A and B, and the column aliases will be "group_a" and "group_b" respectively. The aggregation will be performed with a GROUP BY using the 'day' field.
The resulting queryset will give you the details you want. The rest will be just formatting. You may use the basic JSON serializer Django provides to get the format you want, but if it is for a Web API, you might want to take a look at Django REST Framework, particularly the serializers.