Bidirectional OneToOne relation - Django - python

I have two models (Slot, Appointment). On the appointment one, I have defined a OneToOne relation with the slot model, so good so far; thanks to the RelatedManager I can access the appointment from the slot object but this's done at python level and I need (for some future changes) appointment_id column to be created on the Slot table.
class Slot(models.Model):
start_at = models.DateTimeField()
end_at = models.DateTimeField()
duration = DateTimeRangeField(
null=True,
blank=True
)
# Bidirectional
appointment = models.OneToOneField(
"appointments.Appointment",
on_delete=models.SET_NULL,
related_name="slot",
blank=True,
null=True
)
class Appointment(models.Model):
slot = models.OneToOneField(
Slot,
on_delete=models.SET_NULL,
related_name="appointment",
null=True,
blank=True,
)
The code above will raise some errors when trying to create the migrations like:
appointments.Appointment.slot: (fields.E302) Reverse accessor for 'Appointment.slot' clashes with field name 'Slot.appointment'.
HINT: Rename field 'Slot.appointment', or add/change a related_name argument to the definition for field 'Appointment.slot'.
Basically, due to the python part of the ORM, you can't define the same field on both models.
Any idea of how to achieve this. I guess I will have to overwrite some parts of the RelatedManager. I could make this throw SQL but then it won't be clear on the code level.

Related

How to get value from the different django model in DTL?

I am having 3 different models - User, Thread and UserProfile.
User model contains information like ID, First_name and Last_name.
Thread model contains information like
class Thread(models.Model):
first_person = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='thread_first_person')
second_person = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,related_name='thread_second_person')
updated = models.DateTimeField(auto_now=True)
and UserProfile model,
class UserProfile(models.Model):
custom_user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
picture = models.ImageField(default='profile_image/pro.png', upload_to='profile_image', blank=True)
when I am trying to get all the threads from Thread model and pass it from views.py to my HTML template then I can access User model fields like -
{{ thread.second_person.ID}} {{ thread.second_person.First_name}}
But how can I access picture field from UserProfile with the help of custom_user ?
I'm a little confused: you say you have three models User, Thread and UserProfile. But inside your UserProfile model you reference a CustomUser model as the one-to-one relationship for the custom_user field. Then in your Thread model you reference a plain User model as the FKs for the first_person and second_person fields. Do you have 3 models or 4?
Assuming you only have 3 models, and that CustomUser is actually just User, then what you're trying to achieve should be doable. However you may need to change your conventions regarding related names to best practices in order to do so cleanly.
I have set up the models I think you need roughly below, and the code needed to access the relevant parts of each model within the template layer:
#models.py
class User(AbstractBaseUser):
# User Model Code
class Thread(models.Model):
first_person = models.ForeignKey(
User,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='thread_first_persons' # Note the Plural
)
# Related name for a FK is a one-to-many relationship
# (i.e. 1 User can be first_person on many threads)
# This may not be your desired behaviour, but it is possible on current set-up
second_person = models.ForeignKey(
User,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='thread_second_persons'
) # As above
updated = models.DateTimeField(auto_now=True)
class UserProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE
related_name='user_profile'
)
# Note: custom_user is confusing nomenclature.
# The above is best practice for User <> UserProfile 1-to-1 relationships
picture = models.ImageField(
default='profile_image/pro.png',
upload_to='profile_image',
blank=True
)
Now when you want access to the UserProfile from the Thread object, you can do so as follows inside a template:
{{ thread.second_person.user_profile.picture }}
Side Note: in your views.py file, if you are sending just the thread to your template, then to save your database several queries I would optimise with the following select_related parameters:
#views.py
threads = Thread.objects.select_related(
'first_person', 'first_person__user_profile',
'second_person', 'second_person__user_profile'
).all()
thread = Thread.objects.select_related(
'first_person', 'first_person__user_profile',
'second_person', 'second_person__user_profile'
).get(id=id)

Why is model._meta.get_fields() returning unexpected relationship column names, and can this be prevented?

Imagine I have some models as below:
class User(AbstractUser):
pass
class Medium(models.Model):
researcher = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, related_name="medium_researcher")
old_medium_name = models.CharField(max_length=20, null=True, blank=True)
class Uptake(models.Model):
material_quality = models.CharField(max_length=20, null=True, blank=True)
medium = models.ForeignKey(Medium, on_delete=models.CASCADE, blank=True, null=True, related_name="uptake_medium")
Now I have a function to return all column names to generate some overview in my HTML as such:
from database.models import Medium
MODEL_HEADERS=[f.name for f in Medium._meta.get_fields()]
MODEL_HEADERS
['uptake_medium', 'id', 'researcher', 'old_medium_name']
Why does this return uptake_medium? As this is a ForeignKey relation set within the Uptake model, it should only be present within the Uptake model right? When I review the admin models this column does not show up, neither in the db.sqlite3 model when checking Uptake, so it seems to be sort of hidden, and only show up when requested with _meta. The relationship seems to be correct... This is causing a lot of problems with my code, and it would be great if only the 'non-meta' columns only could be returned. How should I approach?
Why does this return uptake_medium? As this is a ForeignKey relation set within the Uptake model, it should only be present within the Uptake model right?
You can access the relation in reverse, for example:
my_medium.uptake_medium.all()
to obtain all Updates related to the Medium instance named medium.
You can also filter on that field, for example:
Medium.objects.filter(uptake_medium__material_quantity=42)
hence it is accessible just like any field.
You can filter with:
from django.db.models.fields.reverse_related import ManyToOneRel
[f.name for f in Medium._meta.get_fields() if not isinstance(f, ManyToOneRel)]

Django ManyToMany alternative pros and cons

I was developing a chat system with channels and have this models for a thread (some attributes removed for simplicity's sake):
class Thread(models.Model):
name = models.CharField(max_length=50, null=True, blank=True)
users = models.ManyToManyField('auth.User')
I realized it is also possible to implement it like this:
class Thread(models.Model):
name = models.CharField(max_length=50, null=True, blank=True)
class ThreadUsers(models.Model):
thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
What are the advantages/disadvantages of using one over the other?
All what you do - is the same. For your last example with custom M2M through model you can add M2M declaration users in Thread:
class Thread(models.Model):
name = models.CharField(max_length=50, null=True, blank=True)
# M2M declaration, which use your ThreadUsers
users = models.ManyToManyField('auth.User', through='ThreadUsers' )
class ThreadUsers(models.Model):
thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
Pros:
You create model self
you can change behavior your M2M connection manually.
You can add additional fields in M2M through connection.
You have full control of this model.
Cons:
problem with m2m connections in django admin.
too much additional code, you can get a hard-to-find errors.
some fields/widgets don't want to work with M2M.through.
all was happened is your problem, this is not tested. Auto-through relation is tested in box.
we have in our projects 50/50 M2M-autothrough vs M2M-manualthrough. if I want to have more control on the models and realations - i use custom through.
p.s. in M2M-autothrough case Django created SomethingLikeYourThreadUsers Model and Table automatically.

Django ForeignKey create

I want to assign many Region to the UserProfile model, how to do it?
the code
class Region(models.Model):
name = models.CharField(max_length=30)
created_at = models.DateTimeField(auto_now=True)
class UserProfile(models.Model):
user = models.OneToOneField(
region = models.ForeignKey(Region, on_delete=models.CASCADE, null=True, blank=True)
The relation you describe is not a ForeignKey, which means that a UserProfile has (at most) one related Region, but a ManyToManyField [Django-doc].
A ManyToManyField thus means that a region can be related to zero, one, or more UserProfiles, and a UserProfile can be related to zero, one, or more Regions.
You can thus change the models to:
class Region(models.Model):
name = models.CharField(max_length=30)
created_at = models.DateTimeField(auto_now=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
regions = models.ManyToManyField(Region)
In a relational database this is implemented by adding an extra (hidden) table with ForeignKeys to Regions and UserProfiles. But the Django ORM works in a "transparant" way and thus hides the implementation details.
See the documentation for more information on how to "populate" such relation.
from django.contrib.auth.models import AbstractUser
class UserProfile(AbstractUser):
regions = models.ManyToManyField(Region,related_name='User')
I think this is the ideal way to implement what you need. Using ManyToManyField allows you to map userprofile object to more than one region object and vice versa.
Also, Inheriting Abstract User allows you to add region field to Django User Table, which is better than creating another table for linking user to and region field.

Multiple foreign key fields in abstract Django class

I have an abstract base class that declares two foreign key fields to the user model:
class BaseModel(models.Model):
updated = models.DateTimeField(null=True)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, related_name="updated_by")
created = models.DateTimeField(null=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, related_name="created_by")
class Meta:
abstract=True
I have multiple classes that inherit from this class. When I run makemigrations, I get the following error for each possible class-pair and for both created_by and updated_by:
myapp.ClassA.updated_by: (fields.E305) Reverse query name for 'ClassB.updated_by' clashes with reverse query name for 'ClassB.updated_by'.
HINT: Add or change a related_name argument to the definition for 'ClassA.updated_by' or 'ClassB.updated_by'.
Even though I already have a related_name set. It works fine with just one of the two foreign key fields declared.
Is it possible to have two foreign key fields to the same model in an abstract class, and if so, how do I set it up?
This is the expected behavior as mentioned in the documentation.
To work around this problem, when you are using related_name in an abstract base class (only), part of the name should contain '%(app_label)s' and '%(class)s'.
class BaseModel(models.Model):
updated = models.DateTimeField(null=True)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, related_name="updated%(app_label)s_%(class)s_related")
created = models.DateTimeField(null=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, related_name="created%(app_label)s_%(class)s_related")
class Meta:
abstract=True
Since you use the related_name more than once, in model classes you inherit, then related name for the user model is not clear and clashes.
You will have to set a different related_name for each model.

Categories