Django Models multiple foreign key relationship - python-3.x

say I have a model, e.g.,:
class Topic(models.Model):
date = models.DateField(null=False, blank=False)
subject = models.ForeignKey(Subject, blank=False, null=False, on_delete=models.CASCADE)
topic_id = models.PositiveIntegerField(null=False, blank=False)
aggregate_difficulty = models.PositiveIntegerField(null=False, blank=False)
class Meta:
constraints = [models.UniqueConstraint(fields=["subject", "date", "topic_id"], name="topic_unique")]
And we have another model, e.g.,:
class Module(models.Model):
date = models.DateField(null=False, blank=False)
subject = models.ForeignKey(Subject, blank=False, null=False, on_delete=models.CASCADE)
topic_id = models.PositiveIntegerField(null=False, blank=False)
content = models.TextField()
difficulty = models.PositiveIntegerField(null=False, blank=False)
How can I create a foreign key relationship from module to topic using the three fields: date, subject and topic_id?
I would like to have this format, so the person inserting into the database would not have to find out the auto-generated topic id before inserting into the module table.
There are many modules to one topic and many topics to one subject.

Change your relations like so:
class Subject(models.Model):
...
class Module(models.Model):
subject = models.ForeightKey('Subject', ..., related_name='modules')
class Topic(models.Model):
module = models.ForeignKey('Module', ..., related_name='topics')
Then you can check a the subject for a topic like so:
topic = Topic.objects.get('<topic-id>')
subject = topic.module.subject
Or find all of the topics for a given module:
module = Module.objects.get('<module-id>')
topics = module.topics.all()
If you change the topic model to this, you can validate that any subject on a topic must match the subject on a module like so:
from django.core.exceptions import ValidationError
class Topic(models.Model):
module = models.ForeignKey('Module', ..., related_name='topics')
subject = models.ForeignKey('Subject', ..., related_name='topics')
def save(self, *args, **kwargs):
if self.module.subject != self.subject:
raise ValidationError('The subject must match the subject on the module!')
return super().save(*args, **kwargs)
Finally, the date field isn't another model, so you don't want a foriegn key relation, you really just want a unique together constraint, which you have already implemented.

Related

how do I get foreighkey of foreighkey without a loop in django?

It is a Django project, I am trying to create a wishlist (many-to-many will not help because I need DateTime of getting that wished item in the wishlist).
class Client(models.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField()
class WishItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
client = models.ForeignKey(Client, related_name="wishlist", on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True)
What I could do is only this:
wishlist = Client.objects.wishlist.select_related('product').all()
wish_products = [item.product for item in wishlist]
But I need something like this, without a loop but with a single SQL query and single line
wishlist = Client.objects.wishlist.product.all()
When I try to run this code I get an error AttributeError: 'RelatedManager' object has no attribute 'product'
You can .filter(…) [Django-doc] and then .order_by(…) [Django-doc] with:
Product.objects.filter(wishitem__client__user=my_user).order_by('wishitem__added_at')
You can make it more covenient to query by spanning a ManyToManyField with your WishItem:
class Client(models.Model):
# …
wishlist = models.ManyToManyField(
'Product',
through='WishItem'
)
class Product(models.Model):
# …
pass
class WishItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
client = models.ForeignKey(Client, related_name='wishitems', on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True)
then you can query with:
Product.objects.filter(client__user=my_user).order_by('wishitem__added_at')
It will also make querying for the .wishlist of the Client more covenient, of the Products where the .client_set is a manager that manages the Clients that have that Product on the wishlist.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Many to many relationship will fix the problem you can add extra fields to your WishItem class you can try this :
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField()
class Client(models.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
WishProducts = models.ManyToManyField(Product,through='WishItem')
class WishItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
client = models.ForeignKey(Client, on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True)

retrieve data from manytomany field in the sequence of it was saved django

I Have a Invoice system where employee or staff can create invoice and can add multiple product and quantity for the specific customer . as i am using mysql i cant take json data or an array data .so i was taking the quantity and price( after discount and other modificaion) as a string and then when showing or printing the invoice i used regex to find the quantity and price .i added product ids in a manytomay field from where i am getting the product name and selling price. while showing the data on printing page in when i use zip the products are showing as the id of the product so i want to retrive the data the way it is being saved . or could you tell me any way to do it more easier way?
Here is my models.py
class Invoice(models.Model):
customers = models.ForeignKey(Customer, on_delete=models.CASCADE)
products = models.ManyToManyField(Product)
total = models.FloatField()
date = models.DateField(auto_now_add=True)
amounts = models.CharField(max_length=500, default="00")
quantity = models.CharField(max_length=500, blank=True)
def save(self, *args, **kwargs):
if not Invoice.objects.count():
self.id = 20210001
else:
self.id = Invoice.objects.last().id + 1
super(Invoice, self).save(*args, **kwargs)
Here is my views.py of printing page function
def final_billing(request, id=None):
pk = id
obj = Invoice.objects.get(id=pk)
products = obj.products.all()
customer = Customer.objects.get(id=obj.customers.id)
amn = obj.amounts
qt = obj.quantity
list_of_comma = re.findall("[\d+.+\d]+", amn)
amnts = [float(n) for n in list_of_comma]
list_of_quantity = re.findall('[0-9]+', qt)
qty = [int(n) for n in list_of_quantity if n.isdigit()]
products = list(products)
both = zip(products,amnts,qty)
return render(request, 'finalbill.html',{'bills': obj, "due": customer, "both": both})
I want it to be retrieved the product objects in the sequence of it was saved
The query can only be sorted with a specific field, Django cannot guess otherwise, so in your case the best case is to sort your products by the date they were created, for example :
obj.products.all().order_by("created")
This suppose that you have a "created" field that is added each time a product is save in your database.
Another way of doing it is to specify the through option, from the documentation :
you can use the through option to specify the Django model that represents the intermediate table that you want to use.
The most common use for this option is when you want to associate extra data with a many-to-many relationship.
The through table contains an the primary key of the relation, you can use that to retrieve the sequence in which your objects were added.
for example :
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
Through Field

Annotate a Django Queryset Using a 'related_name' Across a ManyToMany Relationship (NO 'Count', 'Avg', 'Max', ...)

I have models in my code similar to the following:
class CompanyProject(models.Model):
""" This class holds project related information read in from the 'project'
custom 'manage.py' command.
"""
project_number = models.IntegerField(blank=False, null=False, unique=True)
project_worktype = models.CharField(blank=True, max_length=255, null=True)
created_at = models.DateTimeField(auto_now_add=True, blank=False, null=False)
updated_at = models.DateTimeField(auto_now=True, blank=False, null=False)
last_seen = models.DateField(blank=False, null=False)
def get_project_subtypes(self):
subtypes = self.project_subtype.all()
return [ subtype.project_subtype for subtype in subtypes ]
class Meta:
ordering = ['project_number']
class CompanySubType(models.Model):
class CompanySubTypeChoices(models.TextChoices):
G1A = '1A', _('1A')
G1B = '1B', _('1B')
G2A = '2A', _('2A')
G2B = '2B', _('2B')
G3A = '3A', _('3A')
G3B = '3B', _('3B')
company_project = models.ManyToManyField(CompanyProject, related_name='project_subtype')
project_subtype = models.CharField(blank=False, choices=CompanySubTypeChoices.choices, max_length=2, null=False)
class ListEntry(models.Model):
list_project = models.OneToOneField(CompanyProject, on_delete=models.CASCADE, related_name='list_project')
list_reviewer = models.ForeignKey('auth.User', on_delete=models.CASCADE, related_name='+')
I would like to return a set of annotated ListEntry objects annotated with a list of ALL of the project subtypes identified with the ListEntry project.
Eventually, I need to be able to pass this annotated data to a Django REST Framework serializer but I need to get the annotation working like I want it to first.
My problem is that I can annotate just fine doing something like this:
list_entry_qs = ListEntry.objects.prefetch_related(
'list_project', 'list_reviewer'
).annotate(
subtypes=F('list_pmatt__project_subtype__project_subtype')
).all()
and it works just fine. The issue I am having is that the query set that's returned from that command duplicates the list_entry object if it has more than one subtype annotation.
For example, if a project numbered 1234 has two project subtypes, '1A' and '3A', I get two list_entry objects: one with subtype annotation '1A' and a separate list_entry object for the same project with subtype annotation '3A'.
I would like a SINGLE object returned for project 1234 that has a subtype annotation of '1A', '2A' that will eventually be serialized to a JSON object.
There has to be an easy way to do this?
OK, After spending literally 8 hours on this, I finally found out how to do it:
from django.contrib.postgres.aggregates.general import ArrayAgg
q = ListEntry.objects.prefetch_related(
'list_pmatt', 'list_reviewer', 'list_pmatt__project_subtype'
).annotate(
my_subtype=ArrayAgg('list_pmatt__project_subtype__project_subtype')
)
>>> q[2].my_subtype
['1A', '3A']

Get Foreign Objects instead of QuerySet of List of ID's

single line of code to get the Foreign Key objects instead of QuerySet of UUID's.
I have three Models,
Student, College, Member
Models are defined similar to below
class Student(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name=models.CharField(max_length=128)
class College(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name=models.CharField(max_length=128)
class Member(models.Model):
student= models.ForeignKey(Student, related_name="students", on_delete=models.CASCADE)
college= models.ForeignKey(College, related_name="colleges", on_delete=models.CASCADE)
Where Member contains of Student and College as Foreign Key fields
Now I want to get all the Students who belong to a particular College based on college_id which should be filtered from the Member model
How I am doing now
student_ids = Member.objects.filter(college_id=college_id).values("student")
Now students is a QuerySet of list of UUID's of Student
As I need actual Student objects instead of QuerySet List of UUID's
students = Student.objects.filter(id__in=[str(uid) for uid in student_ids])
I feel this is an improper way. Can anyone suggest a single line of code to get the Foreign Key objects instead of QuerySet of UUID's. Thanks in advance
You can filter with:
Student.objects.filter(students__college_id=college_id)
The related_name=… parameter [Django-doc] however is the name of the relation in reverse. It thus makes more sense to rename these for example to:
class Member(models.Model):
student = models.ForeignKey(
Student,
related_name='members',
on_delete=models.CASCADE
)
college = models.ForeignKey(
College,
related_name='members',
on_delete=models.CASCADE
)
then you filter with:
Student.objects.filter(members__college_id=college_id)
You can also span a conceptual ManyToManyField [Django-doc] between a College and Student:
class Student(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=128)
colleges = models.ManyToManyField(
'College',
related_name='students'
through='Member'
)
Then you can filter with:
Student.objects.filter(colleges=college_id)

How can I join two child tables using Django Querset API?

I have a model with two child tables and one parent table. Here is the sample model classes.
# Stores list of unique category names
class Category(models.Model):
category = models.CharField(max_length=20, unique=True, validators=[MinLengthValidator(limit_value=5)])
name = models.CharField(max_length=20, validators=[MinLengthValidator(limit_value=8)])
# Parent class for the next two child classes
class DailyLog(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=100, validators=[MinLengthValidator(limit_value=8)])
code = models.CharField(max_length=4, validators=[MinLengthValidator(limit_value=3)])
suggested_values = models.CharField(max_length=100, null=True, blank=True)
# First child class defines display order for dailylog items
class DailyLogDisplayOrder(models.Model):
category_item = models.ForeignKey(DailyLog, on_delete=models.CASCADE)
display_order = models.PositiveIntegerField()
# Second Child class publishes dailylog on a daily bases
class DailyLogCheckList(models.Model):
daily_task = models.ForeignKey(DailyLog, on_delete=models.CASCADE)
publish_date = models.DateField(auto_now=True)
daily_task = DailyTaskCategoryManager() # Log manager to get records per category
How do I perform a cartesian product query? The last column comes from the first child table Dailylogdisplayorder. Here is the raw sql.
select daily_task_id, checklist.publish_date, disporder.display_order
from dailylogchecklist checklist, compliance_dailylogdisplayorder disporder
where checklist.daily_task_id = disporder.category_item_id and checklist.publish_date='2020-07-12'
I have tried using cursor.execute() method per Django documentation. However, I am not able to figure out how to return results in QuyerySet. And also looking for a better way to combine child columns using QuerySet. The return queryset is assigned to a formset.
class DailyTaskCategoryManager(models.Manager):
def with_displayorder(self, user):
from django.db import connection
dtrange = datetime.today().date()
with connection.cursor() as cursor:
cursor.execute("select daily_task_id, checklist.publish_date, disporder.display_order
from dailylogchecklist checklist, compliance_dailylogdisplayorder disporder
where checklist.daily_task_id = disporder.category_item_id and
checklist.publish_date=%s", [dtrange])
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], daily_task_id=row[1], publish_date=row[2])
p.display_order = row[3]
result_list.append(p)
return result_list
I already answered to a similar question, You can use prefetch_related() to get the related child table data. Check this answer
https://stackoverflow.com/a/71571509/9561654

Resources