How to describe m2m triple-join table in model (Django) - python

My question is pretty much the same as this question, except that ALL relationships should be many-to-many.
I have the following classes in my models.py (somewhat simplified):
class Profile(models.Model):
# Extending the built in User model
user = models.ForeignKey(User, unique=True)
birthday = models.DateField()
class Media(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(max_length=2000)
class Role(models.Model):
name = models.CharField(max_length=50)
What I want is a junction table that looks something like this:
CREATE TABLE `media_roles` (
`media_id` bigint(20) unsigned NOT NULL,
`profile_id` bigint(20) unsigned NOT NULL,
`role_id` bigint(20) unsigned NOT NULL
)
John Doe - Director, Writer, Producer
Jane Doe - Executive Producer
...
This is what I've used so far:
class MediaRole(models.Model):
media = models.ForeignKey(Media)
user = models.ForeignKey(Profile)
role = models.ForeignKey(Role)
But isn't there any better way of doing this, not involving the creation of a separate class in the model?

What about separating the two m2m relationships?
class Profile(models.Model):
...
medias = models.ManyToManyField(Media, related_name='profiles')
roles = models.ManyToManyField(Role, related_name='profiles')
In this way Django create two association tables for you, and you can utilize the convenient related fields like this:
profile = Profile.objects.get(user=someone)
print profile.medias.all()
print profile.roles.all()

Related

How to make more than one fields primary key and remove auto generated id in django models

Suppose in a relational database schema we have a student, a subject and a teacher which connect to each other with a relation teaches. Also, the relation has an attribute time that stores the time of the lesson. This is the most complete yet simplified example I can think to describe my case. Now, the most pythonic and django-wise way I can think of trying to reach a correct solution is, after creating a model class for student, subject and teacher, to create a new class Teaches, which has the foreign keys for the three other classes; also it has the property date field for time. This class would look something like this:
class Teaches(models.Model):
teachers = models.ForeignKey(Teacher, on_delete_models.CASCADE)
subjects = models.ForeignKey(Subject, on_delete_models.CASCADE)
students = models.ForeignKey(Student, on_delete_models.CASCADE)
time = models.DateField
class Meta:
constraints = [
fields=['teachers', 'subjects', 'students']
name='teacher_subject_student_triplet'
]
I added the Meta class because this is what this answer recommends as the correct approach.
The problem is that that in the migrations file I can still see the id field. The only way I've seen there is to remove it is to set another field as Primary Key, but in my case I cannot do that, having more than one keys. Any suggestions?
=========== model.py =============
from django.db import models
class TeacherModel(models.Model):
teacher_code = models.CharField(max_length=255)
def __str__(self):
return self.teacher_code
class SubjectModel(models.Model):
subject_code = models.CharField(max_length=255)
def __str__(self):
return self.subject_code
class StudentModel(models.Model):
student_code = models.CharField(max_length=255)
def __str__(self):
return self.student_code
class Teaches(models.Model):
custom_primary_key = models.SlugField(primary_key=True,blank=True)
teacher = models.ForeignKey(TeacherModel, on_delete=models.CASCADE)
subject = models.ForeignKey(SubjectModel, on_delete=models.CASCADE)
student = models.ForeignKey(StudentModel, on_delete=models.CASCADE)
time = models.DateField
#property
def make_key(self):
new_key = str(self.teacher.teacher_code + self.subject.subject_code + self.student.student_code)
return new_key
def save(self, *args, **kwargs):
self.custom_primary_key = self.make_key
super(Teaches, self).save(*args, **kwargs)
========= Output ==============
You can remove autogenerated id by adding primary_key=True, see below code:
class Person(models.Model):
username = CharField(primary_key=True, max_length=100)
first_name = CharField(null=True, blank=True, max_length=100)
setting a field to primary_key=True automatically makes it unique and not null.
In settings.py:
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Controls the automatic generation of primary keys of each model if defined in settings.
Read this article:
Set AutoField or BigAutoField on a per model basis

How to use values and distinct on different field on Django ORM?

currently I have terms on my applications and one user can have a lot of terms registered, and my current model is like this
class Term(models.Model):
id = models.UUIDField("id", default=uuid.uuid4, editable=False, primary_key=True)
user_id = models.PositiveIntegerField("user id", default=None)
name = models.CharField()
sometimes I need to do a query to get all the users who have terms registered, so I do the following query:
Term.objects.filter(active=True)
.order_by("user_id")
.values("user_id")
.distinct()
and this is enough to solve my problems, but now I'll change my model and it will look like this:
class Term(models.Model):
id = models.UUIDField("id", default=uuid.uuid4, editable=False, primary_key=True)
user_id = models.PositiveIntegerField("user id", default=None)
name = models.CharField()
shared_with = ArrayField(models.PositiveIntegerField("id do usuario"), blank=True) # New
How you can see, I've added a new field named shared_with, that basically is a array of user ids which I want to share terms, So now I need to make a query who will return all ids who can have terms registered (shared_with included). So if i register a Term with user_id = 1 and shared_with = [2,3], my query need to return [1,2,3].
I've solved this problem today with the following code, but I think I can do this just using django ORM and one query:
users = set()
for user in (
Term.objects.filter(active=True)
.order_by("user_id")
.values("user_id")
.distinct()
):
users.add(user["user_id"])
for user in (
Term.objects.filter(active=True)
.order_by("user_id")
.values("shared_with")
):
for user_id in user["shared_with"]:
users.add(user_id)
print(users) # {1,2,3}
If someone knows how to do it and can share the knowledge, I will be grateful.
I don't recommend using the PositiveIntegerField and ArrayField as relations between tables, you can use ForeignKey and ManyToManyField instead, in your case what I understand is a user can have many Terms and a Term can be shared among many users, so the perfect solution is to add ManyToManyField in your User model
class User(AbstarctUser):
... (your fields)
terms = models.ManyToManyField(Term, related_name="users")
and Term model will be like:
class Term(models.Model):
id = models.UUIDField("id", default=uuid.uuid4, editable=False, primary_key=True)
name = models.CharField(max_length=200)
active = models.BooleanField(default=True)
... (other fields)
in that case, if you want to extract user ids with active terms, you can get it as following :
users = User.objects.filter(terms__active=True).distinct().values_list("id", flat=True)

Django multiple relations in one model

I have been trying to create a model that could represent the form as it is, tried creating an EntryForm model which is linked to EntryFormTable where then each column in the table is a model class all linked to the table, but then this proved to be a long way and one that doesn't even work, maybe there's a short or even a working method to represent this in django models,
It is recommended to model the "things" as they are in real life, and not as they would appear on the screen. So don't create a model called EntryForm, EntryFormTable or EntryFormColumn, but rather name them what they are. Example based on your image:
class CoveredWorkSet(Model):
school: CharField()
learning_area: CharField()
teacher: ForeignKey(Teacher) # or just CharField if you don't have them in your database
role:
grade: CharField()
class CoveredWork(Model):
covered_work_set = ForeignKey(CoveredWorkSet, related_name='records')
date = DateField()
lesson = CharField()
work_done = BooleanField()
reflection = TextField()
class Signature(Model):
"""
Represents a signature on either a CoveredWork record or a complete CoveredWorkSet
"""
ROLE_CHOICES = [
('subject', 'Subject teacher'),
('class', 'Class teacher'),
('head', 'Head teacher'),
]
teacher = ForeignKey(Teacher, related_name='signatures')
role = CharField(choices=ROLE_CHOICES)
covered_work_set = ForeignKey(CoveredWorkSet, null=True)
covered_work = ForeignKey(CoveredWor, null=True)
date = DateTimeField()
signature = ImageField()

m2m 'through' field in django models throwing this error: 'M2M field' object has no attribute '_m2m_reverse_name_cache'

Hey guys I am trying to add a m2m through field to have assistants to my 'Department' model to call like department.assistants.all(), but while doing so, I am getting this error AttributeError: 'ManyToManyField' object has no attribute '_m2m_reverse_name_cache'.
This is my model:
class Department(models.Model):
id = models.BigAutoField(primary_key=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
assistants = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Assistants', related_name='dep_assistants',
symmetrical=False)
class Assistants(models.Model):
id = models.BigAutoField(primary_key=True)
department = models.ForeignKey(Department, related_name='of_department', on_delete=models.CASCADE)
assistant = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='dt_assistant',
verbose_name="Department Assistant", on_delete=models.CASCADE)
added = models.DateTimeField(auto_now_add=True)
I am pretty new to this concept. Can someone tell me what I did wrong here?
Thanks
The way you have defined your models the queries seem too confusing. Try how models are defined below and then try the query.
You did not mention the through_field attribute in the many to many field definition. check the docs: https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ManyToManyField
class Department(models.Model):
# i think this is not needed. Also id is a protected keyword in python.
# id = models.BigAutoField(primary_key=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
assistants = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Assistants',
related_name='departments', through_fields=("department", "assistant"))
# model name should never be prural. It is singluar becuase it is the name of the object.
class Assistant(models.Model):
# i think this is not needed. Also id is a protected keyword in python.
# id = models.BigAutoField(primary_key=True)
department = models.ForeignKey(Department, on_delete=models.CASCADE)
assistant = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name="Department Assistant", on_delete=models.CASCADE)
added = models.DateTimeField(auto_now_add=True)
# how to query assistants from departments
# you will get objects of User model
qs = department.assistants.all()
# how to query departments from assistants
# you will get objects of Department model
qs = user.departments.all()
# If you want to query the Assistant model
# from department object
qs = department.assistant_set.all()
# from assistant object
qs = user.assistant_set.all()
# in either case you will get the objects of Assistant model
for i in qs:
print(i.added, i.department, i.assistant)
Try this and let me know if you still get the error.
My suggestion is to name the assistant field on the Assistant model as user. This way you will not need to define through_field on the many to many field.
If one assistant relates to only one department - this is relation one-to-many. (One department has many assistants) In code would be:
class Assistant(models.Model):
...
department = models.ForeignKey(Department)
No need for a special reference on Department. To get all assistants:
assistants = models.Assistant.objects.filter(department=department)
Or create a property on a class Department:
#property
def assistants(self):
return models.Assistant.objects.filter(department=self)
If one assistant relates to many departments (and each department has many assistants), it is many-to-many relationship and there should be additional class between them:
class Assignment(models.Model):
assistant = models.ForeignKey(Assistant)
department = models.ForeignKey(Department)
class Department(models.Model):
...
assignment= models.ForeignKey(Assignment)
class Assistant(models.Model):
...
assignment = models.ForeignKey(Assignment)
So here to query assistants of the department:
assistants = models.Assistant.objects.filter(
assignment__in=models.Assignment.objects.filter(
department=department
)
)

Not sure I understand dependancy between 2 django models

I am struggling to understand django models relationship.
I have this arborescence:
A train have cars, and those cars are divided into parts. Then those parts all contains different references.
Like, for exemple, all the trains have the 6 cars, and the cars 6 parts. Each part have x reference to be associated.
I would like to use all of them in a template later on, where the user can select the train, the car and the part he worked on, then generate a table from his selections with only the references associated to the parts he selected.
It should update the train and the car (I'm trying to update a stock of elements for a company)
I dont really understand which model field give to each of them. After checking the doc, Ive done something like this but i am not convinced:
class Train(Car):
train = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Car(Part):
car = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Part(Reference):
part = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Reference(models.Model):
reference = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
def __str__(self):
return self.reference
Can someone please help me understand this so I can do well ? Thanks!!
1-)if you add abstract = True in your Model Meta class, your class doesn't created on database as a table. If you store data for any class, you mustn't define abstract = True.
2-)For relations, you can use models.ForeignKey . If you add a class into brackets of another class, it names: inheritance.(You can think like parent-child relation). In database management, we can use foreignkey for one-to-many relationship.
3-)In Django ORM, id field automatically generated. So you don't need to define id field.
If I understand correctly, also you want to store parts of user's selected.
So, your model can be like that:
class Train(models.Model):
name = models.CharField(max_length=200) # I think you want to save name of train
class Car(models.Model):
train = models.ForeignKey(Train,on_delete=models.Cascade)
name = models.CharField(max_length=200)
class Part(models.Model):
car = models.ForeignKey(Car,on_delete=models.Cascade)
name = models.CharField(max_length=200)
class Reference(models.Model):
part = models.ForeignKey(Part,on_delete=models.Cascade)
name = models.CharField(max_length=200)
def __str__(self):
return self.reference
#addtional table for storing user's references
class UserReference(models.Model):
user = models.ForeignKey(User,on_delete=models.Cascade)
reference = models.ForeignKey(Reference,on_delete=models.Cascade)
name = models.CharField(max_length=200)
With this definitions, you can store user's definition on UserReference table. And with Django Orm, you can access train object from UserReferenceObject.
#user_reference: UserReference object like that result of UserReference.objects.first()
user_reference.reference.part.car.train.name

Categories