I am making a web app and i wish to use many to many fields in models.I haven't given much thought before and have simply used many to many field in models but now i am thinking how actually these many to many are stored in database and what would be the best approach when using Many to Many Fields in Models.
Approach 1:
class Company(models.Model):
name = models.CharField(max_length = 190,blank = False,null = False)
about = models.CharField(max_length = 260,blank = False,null = True)
website = models.URLField(blank = False)
address = models.OneToOneField(Address,related_name="org_address",on_delete=models.SET_NULL,null=True)
admin = models.ManyToManyField(User,related_name="org_admin",blank = False)
created_on = models.DateTimeField(default = timezone.now)
updated_on = models.DateTimeField(default = timezone.now)
OR
Approach 2:
class Company(models.Model):
name = models.CharField(max_length = 190,blank = False,null = False)
about = models.CharField(max_length = 260,blank = False,null = True)
website = models.URLField(blank = False)
address = models.OneToOneField(Address,related_name="org_address",on_delete=models.SET_NULL,null=True)
created_on = models.DateTimeField(default = timezone.now)
updated_on = models.DateTimeField(default = timezone.now)
class CompanyAdmin(models.Model):
admin = models.ManyToManyField(User,related_name="org_admin",blank = False)
company = models.ForeignKey(Company,related_name="company",on_delete=models.CASCADE)
Do both methods handle many to many fields in the same way ?
I am using MySQL as Database
Update
"To make them Equivalent, Second Approach should have field as ForiegnKey and not many to many"
also to avoid multiple company admin entries Field company should be one to one.
Solution
Do not create join tables as Django ORM Handles this itself.
but now I am thinking how actually these many to many are stored in database.
Django will construct a model that has two ForeignKeys, one to the Company and one to the User model. This model thus corresponds to the junction table [wiki] between the Company and User model.
A ManyToManyField thus does not map on a column. The ManyToManyField is more a concept that enables one to write simpler queries, and provides a more convenient interface.
Do both methods handle many to many fields in the same way ?
No. With the latter you will make two extra tables, one for the CompanyAdmin model, and one for the ManyToManyField between CompanyAdmin and User. It would also result in more JOINs to fetch the companies for which a User is an admin for example, and it would here be possible to construct multiple CompanyAdmins for the same Company, and thus linking the same User multiple times.
You can define however custom attributes in the junction table, by specifying the junction model explicitly with the through=… parameter [Django-doc]:
from django.conf import settings
class Company(models.Model):
admin = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name='org_admin',
through='CompanyAdmin'
)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class CompanyAdmin(models.Model):
admin = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
company = models.ForeignKey(
Company,
on_delete=models.CASCADE
)
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.
Note: Django's DateTimeField [Django-doc]
has a auto_now_add=… parameter [Django-doc]
to work with timestamps. This will automatically assign the current datetime
when creating the object, and mark it as non-editable (editable=False), such
that it does not appear in ModelForms by default.
Related
Consider the following models
in models.py:
class Volunteer(models.Model):
account = models.OneToOneField(
UserAccount,
on_delete=models.CASCADE,
related_name= "volunteer",
primary_key=True)
request_offers = models.ManyToManyField('Request', related_name="volunteers", blank = True)
volunteer_communities = models.ManyToManyField('Community', related_name="volunteers", blank = True)
class Request(models.Model):
req_type = models.ForeignKey('RequestTypes', related_name="requests", on_delete = models.CASCADE, blank = False, null = False)
A volunteer can have many request_offers(type of request).
In the implicit appname_volunteer_request (many-to-many) table, I want another field called date besides the default fields -- id, request_id and volunteer_id.
Is there a way to add an additional field on the implicit table without creating a through table?
Thanks! :)
The simplest and most elegant way to do this is with a through=… model [Django-doc], this will eventually require the least amount of work to keep data in sync. In your through model, that will act as a junction table [wiki], you can specify a DateTimeField or any other field:
class Volunteer(models.Model):
# …
request_offers = models.ManyToManyField(
'Request',
related_name='volunteers',
through='VolunteerRequest',
blank = True
)
class Request(models.Model):
req_type = models.ForeignKey(
'RequestTypes',
related_name='requests',
on_delete = models.CASCADE
)
class VolunteerRequest(models.Model):
volunteer = models.ForeignKey(
Volunteer,
on_delete=models.CASCADE
)
request = models.ForeignKey(
Request,
on_delete=models.CASCADE
)
date = models.DateTimeField(auto_now_add=True)
Making more tables will only require extra logic to keep these tables in sync, and will often make queries more complicated and will make the database less efficient.
Unfortunately, there is no way to do it without a through table.
You can attempt to create one more table where the id of through table will be paired with date, and then change it whenever a signal is fired on thorough table.Other option is to manage it manually by overriding methods of the manager save, delete, create and methods of the relation manager add, clean, remove, set
Information that might help you:
https://docs.djangoproject.com/en/3.2/ref/signals/#post-save
https://docs.djangoproject.com/en/4.0/topics/db/models/#intermediary-manytomany
I have a moderation model :
class ItemModeration(models.Model):
class Meta:
indexes = [
models.Index(fields=['object_id', 'content_type']),
]
unique_together = ('content_type', 'object_id')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
published = models.BooleanField(default=False)
...
A descriptor to attach a moderation object on-the-fly :
class ItemModerationDescriptor(object):
def __init__(self, **default_kwargs):
self.default_kwargs = default_kwargs
def __get__(self, instance, owner):
ctype = ContentType.objects.get_for_model(instance.__class__)
try:
moderation = ItemModeration.objects.get(content_type__pk=ctype.id,
object_id=instance.pk)
except ItemModeration.DoesNotExist:
moderation = ItemModeration(item=instance,**self.default_kwargs)
moderation.save()
return moderation
And a model I want to moderate :
class Product(models.Model):
user = models.ForeignKey(
User,
null=True,
on_delete=models.SET_NULL)
created = models.DateTimeField(
auto_now_add=True,
blank=True, null=True,
)
modified = models.DateTimeField(
auto_now=True,
blank=True, null=True,
)
name = models.CharField(
max_length=PRODUCT_NAME_MAX_LENGTH,
blank=True, null=True,
)
moderation = ItemModerationDescriptor()
Now I can see a product 'published' state easily :
p=Product(name='my super product')
p.save()
print(p.moderation.published)
-> False
The generic relation is useful because I will be able to search the objects to moderate whatever the type is : it could be products, images, comments.
to_moderate_qs = ItemModeration.objects.filter(published=False)
Now, how can I get a filtered list of published products ?
I would like to do something like this
published_products_qs = Product.objects.filter(moderation__published=True, name__icontains='sony')
But, of course, it won't work as moderation attribute is not a Django model field.
How can I do that efficiently ? I am thinking a about an appropriate JOIN, but I cannot see how to do that with django without using raw SQL.
Django has a great built in answer for this: the GenericRelation. Instead of your descriptor, just define a generic relation on your Product model and use it as a normal related field:
from django.contrib.contenttypes.fields import GenericRelation
class Product(models.Model):
...
moderation = GenericRelation(ItemModeration)
Then handle creation as you normally would with a related model, and filtering should work exactly as you stipulated. To work as your current system, you'd have to put in a hook or save method to create the related ItemModeration object when creating a new Product, but that's no different from other related django models. If you really want to keep the descriptor class, you can obviously make use of a secondary model field for the GenericRelation.
You can also add related_query_name to allow filtering the ItemModeration objects based only on the Product content type.
WARNING if you do use a GenericRelation note that it has a fixed cascading delete behavior. So if you don't want ItemModeration object to be deleted when you delete the Product, be careful to add a pre_delete hook or equivalent!
Update
I unintentionally ignored the OneToOne aspect of the question because the GenericForeignKey is a one-to-many relation, but similar functionality can be effected via smart use of QuerySets. It's true, you don't have access to product.moderation as a single object. But, for example, the following query iterates over a filtered list of products and extracts their name, the user's username, and the published date of the related ModerationItem:
Product.objects.filter(...).values_list(
'name', 'user__username', 'moderation__published'
)
You'll have to use the content_type to query the table by specific model type.
like this:
product_type = ContentType.objects.get_for_model(Product)
unpublished_products = ItemModeration.objects.filter(content_type__pk=product_type.id, published=False)
For more details on the topic check contenttypes doc
I am trying to create the following models. There is a ManyToMany relation from Entry to AUTH_USER_MODEL via the EntryLike intermediate model.
class BaseType(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
creation_time = models.DateTimeField(auto_now_add=True)
last_update_time = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Title(BaseType):
text = models.CharField(max_length=100)
description = models.TextField()
class EntryLike(BaseType):
entry = models.ForeignKey(Entry)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class Entry(BaseType):
title = models.ForeignKey(Title, on_delete=models.PROTECT)
text = models.TextField()
user = models.ForeignKey(settings.AUTH_USER_MODEL)
liked_by_users = models.ManyToManyField(settings.AUTH_USER_MODEL, through='EntryLike', through_fields=('entry', 'user'))
Running migrations on the above model scheme throws the error: AttributeError:'str' object has no attribute 'meta'.
Any help in resolving this error would be highly appreciated. Am new to Django & Python, but not to Web Development.
The issue is that settings.AUTH_USER_MODEL is almost certainly not a model instance. It's probably a string that constrains the choices another model can make - settings would be a strange place to leave a model definition.
To do a MTM between the user model and your field above you need need to do:
from django.contrib.auth.models import User
class Entry(BaseType):
title = models.ForeignKey(Title, on_delete=models.PROTECT)
text = models.TextField()
user = models.ForeignKey(User)
def __str__(self):
return self.title
I've added the str function so that it gives a more sensible return when you're manipulating it in admin/shell.
I'd also question whether you need the second set of fields (removed here), as you can use select related between the Entry and EntryLike join table, without any duplication of the fields - you can probably go that way, it's just a bit unnecessary.
Lastly, I'd note that the way I'm using it above just uses the default User object that comes with Django - you may wish to customise it. or extend the base class as you've done here with your own models' base class.
(All of this is predicated on AUTH_USER_MODEL not being a model instance - if it is, can you post the model definition from settings.py? )
This is the first time I am working with DJango.
I am little confused about how my model should look.
Use case is:
There are products.
There are tags.
There are users.
There is a many to many relationship between products and tags.
There is a many to many relationship between users and tags.
I have created two apps right now.
Currently product and tags belong to one app: product
Another app is usrprofile. And I need to add tags to user profile.
Where should Tag reside?
And will tag have reference to both product and user?
Code:
App: Product
class Product(models.Model):
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
name = models.CharField(max_length=300)
class Tag(models.Model):
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
name = models.CharField(max_length=300)
display_name = models.CharField(max_length=300)
product = models.ManyToManyField(Product, through='ProductTag')
class ProductTag(models.Model):
product = models.ForeignKey(Product,null=False)
tag = models.ForeignKey(Tag,null=False)
APP: UsrProfile
class UserProfile(models.Model):
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
email = models.CharField(max_length=300)
Nobody can tell you where your Tag model should best reside in. It's your choice to structure your apps and models. If you want to establish a many-to-many relationship between Tag and UserProfile, you can specify it in the UserProfile model, for instance:
class UserProfile(models.Model):
# ... your other fields ...
tags = models.ManyToManyField('product.Tag')
Note that you have to put the Tag model in a string together with a reference to the product app as shown above. Otherwise, Django will wrongly assume that your Tag model resides in the same app as your UserProfile model. Also, the names of your apps should all be lowercase. Further, it's a good style to give your many-to-many fields plural names, i.e. instead of product in your Tag model use products.
By the way, if you don't need to add additional information to your many-to-many relationships, it's not necessary to define an intermediate model such as ProductTag.
i have a model that is having multiple many to many relation to another model it is as follows:
class Match(models.Model):
"""Model docstring"""
Match_Id = models.AutoField(primary_key=True)
Team_one = models.ManyToManyField('Team',related_name='Team one',symmetrical=False,)
Team_two = models.ManyToManyField('Team',related_name='Team two',symmetrical=False,)
stadium = models.CharField(max_length=255, blank=True)
Start_time = models.DateTimeField(auto_now_add=False, auto_now=False, blank=True, null=True)
Rafree = models.CharField(max_length=255, blank=True)
Judge = models.CharField(max_length=255, blank=True)
winner = models.ForeignKey('Team', related_name='winner',to_field='Team_Name')
updated = models.DateTimeField('update date', auto_now=True )
created = models.DateTimeField('creation date', auto_now_add=True )
what is the best way to implement model like this ?. all though django does not throw any errors when passing the model sql once syncdb is excuted it throws up errors saying there is no unique constraint matching given keys
Are you sure Team_one and Team_two should be ManyToMany fields? Surely, a match only has a single team on each side - in which case these should both be ForeignKeys.
Using spaces in related_name attribute makes me uneasy, but I think the real problem is connected to the use of to_field attribute on the winner field. As far as I know you can set database relations only to unique fields. It doesn't really make sense to relate to another object using a field that may not be unique.
I'm not sure what do you want to achieve by connecting through this particular field. You usually connect models using primary key fields. This still allows you to access any other field on the related object.