How to count foreign key objects with annotation in Django? - python-3.x

Hi all!
New in Django, and confused, help is appreciated!
Have three models:
class Organization(models.Model):
organization_name = models.CharField(max_length=50)
class AppealForm(models.Model):
form_name = models.CharField(max_length=50)
class Appeal(models.Model):
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
appeal_form = models.ForeignKey(AppealForm, on_delete=models.CASCADE)
applicant_name = models.CharField(max_length=150)
Objects of Organization model:
organization_name
Organization 1
Organization 2
Objects of AppealForm model:
form_name
In written form
In oral form
Objects of Appeal model:
organization
appeal_form
applicant_name
Organization 1
In written form
First and Last name
Organization 1
In oral form
First and Last name
Organization 1
In oral form
First and Last name
Organization 2
In written form
First and Last name
Organization 2
In oral form
First and Last name
I'm trying to create a table in the template, like:
Organization
Total amount of appeals
Amount of written form appeals
Amount of oral form appeals
Organization 1
3
1
2
Organization 2
2
1
1
The content in the table contents has to be retrieved from Appeal model, that is rendered to the template.
Question:
How the query look like in views.py using Appeal model?

i recommend you read about how to do complex query in django from this documents section.
Here what your query would be like:
from django.db.models import Count, Case, When, IntegerField
Appeal.objects
.values('organization')
.annotate(
total_appeals=Count('appeal_form'),
written_amount=Count(Case(
When(appeal_form__form_name="In written form", then=1),
output_field=IntegerField(),
)),
oral_amount=Count(Case(
When(appeal_form__form_name="In oral form", then=1),
output_field=IntegerField(),
)),
).order_by()
set the above query to a value and loop through it and see the results. You should get something like this
[{'organization': 'Organization 1', 'total_appeals': 3,
'written_amount': 1, 'oral_amount': 2}, {'organization':
'Organization 2', 'total_appeals': 2, 'written_amount': 1,
'oral_amount': 1}]
Explanations:
values to group by organization
Count to count the form in that organization group
we use Case to add an if condition, then=1 to return value(else return null)
the written_amount need to be integer so we set it in the output_field
conditional part in the documents https://docs.djangoproject.com/en/dev/ref/models/conditional-expressions
why you should use order_by() for grouping queries https://docs.djangoproject.com/en/4.0/topics/db/aggregation/#interaction-with-order-by

Related

Two properties that relate to each other in another model

Sorry that the title might be confusing but I'm not native english speaker and very new to django terms.
I have a scenario like this: A department can have many branches. I have a student model where he has two properties, Department and Branch.
When I select his department , I want it to accept (and show in admin panel) only the branches that are related to that department , my code so far is this:
class Entity(models.Model):
id = models.UUIDField(primary_key=True , default = uuid.uuid4, editable=False)
class Department(Entity):
name = models.CharField(max_length=100, null=False)
class Branch(Entity):
name = models.CharField(max_length=100, null=False)
dep = models.ForeignKey(Department, related_name='branches', on_delete=models.CASCADE)
class Student(Entity):
#Some Fields here
department = models.ForeignKey(Department, related_name='students', on_delete=models.CASCADE)
branch = models.ForeignKey(Branch, related_name='students', on_delete=models.CASCADE)
Assuming I have a 2 departments (CE and CS), CE has 2 branches and CS has 3 branches , What I want is, when I choose a student's department in the admin panel, I want the branches to be shown (to select from) only the one that exists on that department , what I'm getting is 5 branches (in this example).
How can I solve this ?
NOTE: I haven't played with anything related to the admin panel except registering the models.
Thanks in advance and sorry if the title or any other part is not very correct.
There are two solutions:
override save() function and check branch.
Check branch inside form by overriding clean_branch()
It's better to implement both.

how to make a field unique based on another filed in django models?

I want to make a field unique based on another field in the same model, this is my model:
class Shop(models.Model):
name = models.CharField(max_length=50)
class Product(models.Model):
name = models.CharField(max_length=50, unique=True)
shop = models.ForignKey(Shop, on_delete=models.CASCADE)
I want the product's name to be unique only based on Shop, for example, if we have the product a from shop a, shop a can not make another product with the name a but shop b can make a product with name a.
for example we have name = models.CharField(unique_for_date=date_field) in models, which make the name unique for the date at date_field.
is there anything like unique_for_date?
can I handle this operation in models or I should try to handle it in view or form?
On your Product table:
class Product(...):
...
class Meta:
unique_together = ('shop', 'name')
This will ensure Products must have a unique name across the Shop they are related to.

Add element in many to many field and preserve order

class Country(Models.Model):
code = models.CharField(max_length=50)
name = models.CharField(max_length=500)
class Meta:
unique_together = (('code', 'name'),)
db_table = 'md_country'
class UserSettings(models.Model):
...
default_countries = models.ManyToManyField(Country, db_table='user_default_countries', related_name='default_countries')
I have two models inside django models, what im trying is when i add Country models to default_countries i want to preserve order. Currently when i append manytomany field django automatically sort by Country name (alphabetical order)
I have this code
# iterate one by one to preserve fetching order
country_models = [Country.objects.get(id=_id) for _id in request.data[default_countries]]
user_settings.default_countries.clear()
for c in country_models:
user_settings.default_countries.add(c)
After this when i inspect user_settings.default_countries i have ordered countries by name in alphabetical order.
I want to preserve when adding element. If i want to add France and Australia and i order the list like that i on the end when i pull data from db i want it to be ordered like that. Now on this example i have Australia then France.
EDIT:
I checked the database and when inserting the data, it insert in right order
For example if i want France(73) then Australia(13), France has smaller id so its inserted first. There is a problem with django when pulling the data from database.
So as I understand correct you want to sort by insert order:
someSetting = UserSettings.objects.first()
countries = someSetting.default_countries.order_by('id')
I found the workaround.
Firstly i defined new property inside model where default_countries is.
#property
def ordered_default_countries(self):
return self.default_countries.all().order_by('-id')
Then in serializer where i serialize this field i just pointed default_countries field to ordered_default_countries.

join the results of two query on each others with Django

I have 2 models, i simplify them for the example:
class CustomerOrder(models.Model):
product = models.ForeignKey(Product, on_delete=models.PROTECT)
isPaid = models.BooleanField(default=False)
and
class EventParticipant(models.Model):
customerOrder = models.ForeignKey(CustomerOrder, on_delete=models.CASCADE)
event = models.ForeignKey(Product, on_delete=models.CASCADE)
What i need to do is to display in a table every participants for an event but link the order to the participant so i can display the isPaid status for each participant.
I think its similar to a join on SQL.
So i tried something like:
participants = EventParticipant.objects.filter(event=event_pk).select_related('customerOrder')
but when i try to access it like
participants.cusomerOrder
i get: 'QuerySet' object has no attribute 'customerOrder'
so i guess is missunderstand something.
Thanks
participants is an EventParticipant QuerySet, whcih is an iterable, So you need to iterate over it
for participant in participants:
print(participant.customerOrder.isPaid)

Showing one-to-many relationship

I am trying to present data from SQL Server in an Excel sheet so that operational users can understand the data. Here's how the data looks in SQL Server:
PersonId Name Address Role Organization
1 John Smith 123 Main St Donor Library
1 John Smith 123 Main St Member Ballet
2 Jane Doe 333 Main St Member Orchestra
As you can see the database contains a one-to-many relationship between a person and the role they play in an organization.
In my Excel I want to show the person record only once and somehow show that this person plays multiple roles and these are the roles.
There are lots of group concatenation methods out there. Some use dynamic sql and others use xml. Here's a simple one if you have a short list of roles known in advance. And this way you can control the order of the listing really easily.
select *,
(
select
substring(
coalesce(min(case when r.Role = 'Donor' then ', Donor' end), '') +
coalesce(min(case when r.Role = 'Member' then ', Member' end), '') +
...
coalesce(min(case when r.Role = 'XXXXXX' then ', XXXXXX' end), '')
, 3, 300)
from PersonRoles pr
where pr.PersonId = p.PersonId
) as Roles
from Person p
I'm not sure how organization fits into your problem but it appears to me that it's part of the role. You should be able to use pr.Role + ' ' + pr.Organization in the case logic for that.

Resources