Django: remove all m2m relations - django

if I have two simple models:
class Tag(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
title = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag, blank=True)
given a Post object with a number of Tags added to it, I know hot to remove any of them, but how to do a mass remove (remove all)? Thanks

Have you tried Post.tags.clear()?

If you need to delete only the relationship for all instance between 2 models then you can do that by accessing the Manager of the relationship table. The m2m relationship table can be accessed via MyModel.relations.through so for deleting the relationships it becomes easy:
MyModel.relations.through.objects.all().delete()
reference:
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ManyToManyField.through

Related

Django import into many to many relationship with custom through table

I am not sure the proper way to import using django-import-export tsv files into many to many relationship tables. What I have done is create a books table, a genre table and a bookgenre through table which contains the foreignkeys to each of the other tables. So what I have is:
class Book(models.Model):
title = models.CharField(max_length=255)
class Genre(models.Model):
genre = models.CharField(max_length=64)
class BookGenre(models.Model):
book_id = models.ForeignKey('Book', on_delete=models.CASCADE)
genre_id = models.ForeignKey('Genre', on_delete=models.CASCADE)
Then I import all three tables and the relationships are working but I can't figure out a way to make a view or template that works effectively. I am thinking there must be a better way to create a many to many relationship and import data into it. Any Ideas?
You don't need to create a pivot table, use many-to-many relationships.
UPDATE
You do not create a m2m model, but a many-to-many relationship that points to a specific model. Below is the implementation.
class Abc(models.Model):
field = models.TextField()
class Xyz(models.Model):
other_field = models.TextField()
m2m_field = models.ManyToManyField(Abc)
If you want to get related data, you will use:
object_xyz.abcs.all()
or object_abc.xyz_set.all()
As I wrote earlier, you can find more detailed information in the documentation.

Can I use unique_together on ManyToMany field?

I have a model OrderPage which is manytomany to Site. In Django admin, I want to restrict the selection of sites(Sites which belong to existing OrderPage can not be selected again). Can I do it with unique_together ? I get an error with following model ManyToManyFields are not supported in unique_together
class OrderPage(models.Model):
description = models.CharField(max_length=255, blank=False)
sites = models.ManyToManyField(Site)
class Meta:
unique_together = (('id', 'sites'),)
class Order(models.Model):
order_page = models.ForeignKey(OrderPage)
class OrderPageAdmin(admin.ModelAdmin):
filter_horizontal = ('sites',)
admin.site.register(OrderPage, OrderPageAdmin)
If an Site can have only one OrderPage, you don't need to worry about unique_together.
Ideally you should subclass Site and use a ForeignKey from that to OrderPage. That would natively give you what you're looking for: each site would be able to have one OrderPage, and each OrderPage multiple Sites. This would be the cleanest but you would have to use your subclass throughout the program in place of the original Site which might be more work than you want right now.
class BetterSite(Site):
order_page = models.ForeignKey('OrderPage')
The dirtier way is to keep your M2M and just set the site as unique, since there should only ever be one entry on each site in the M2M table. You would use a 'through' table so you could set the custom uniqueness value:
class OrderPage(models.Model):
description = models.CharField(max_length=255, blank=False)
sites = models.ManyToManyField(Site, through='OrderPageToSite')
class OrderPageToSite(models.Model):
order_page = models.ForeignKey(OrderPage)
site = models.ForeignKey(Site, unique=True)
(Note that I've left these simple but in your FK fields you should also consider setting on_delete and related_name)

"already exists" error when using ManyToMany relationship between the same two models

class Product( models.Model ):
name = models.CharField(verbose_name="Name", max_length=255, null=True, blank=True)
the_products_inside_combo = models.ManyToManyField('self', verbose_name="Products Inside Combo", help_text="Only for Combo Products", blank=True)
However, I got this error when I tried to put the duplicate values:
From_product-to_product relationship with this From product and To
product already exists.
Screencap of the error.
Each pair (Product, Product) must be unique. This is why you get already exists error.
Behind the scenes, Django creates an intermediary join table to
represent the many-to-many relationship.
What do you want to do is to have many-to-many relationship between two models (nevermind that they are the same) with additional information stored - quantity (so you would have ProductA = 2x ProductB + ....
In order to model this relationship you will have to create intermediary model and use through option. Documentation explains it very well, so have a look:
https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany
Update
Here is minimal working example:
class Product(models.Model):
name = models.CharField(verbose_name='Name', max_length=255, null=True, blank=True)
products = models.ManyToManyField('self', through='ProductGroup', symmetrical=False)
def __str__(self):
return self.name
class ProductGroup(models.Model):
parent = models.ForeignKey('Product', related_name='+')
child = models.ForeignKey('Product', related_name='+')
and admin models:
class ProductGroupInline(admin.TabularInline):
model = Product.products.through
fk_name = 'parent'
class ProductAdmin(admin.ModelAdmin):
inlines = [
ProductGroupInline,
]
admin.site.register(Product, ProductAdmin)
admin.site.register(ProductGroup)
As you can see recursive Product-Product relation is modeled with ProductGroup (through parameter). Couple of notes:
Many-to-many fields with intermediate tables must not be symmetrical, hence symmetrical=False. Details.
Reverse accessors for ProductGroup are disabled ('+') (in general you can just rename them, however, you don't want to work with ProductGroup directly). Otherwise we would get Reverse accessor for 'ProductGroup.child' clashes with reverse accessor for 'ProductGroup.parent'..
In order to have a nice display of ManyToMany in admin we have to use inline models (ProductGroupInline). Read about them in documentation. Please note, however, fk_name field. We have to specify this because ProductGroup itself is ambiguous - both fields are foreign keys to the same model.
Be cautious with recurrency. If you would define, for example, __str__ on Product as: return self.products having ProductGroup with the same parent as the child you would loop infinitely.
As you can see in the screencap pairs can be duplicated now. Alternatively you would just add quantity field to ProductGroup and check for duplication when creating objects.

Django Relating 2 Models

I'm not too sure how to find this through google and searching hasn't been leading me anywhere.
How do you relate 2 models in such a way that if you delete one entry in the admin panel, the other will automatically be deleted?
Thank You for any help.
EDIT: Updated with example. I want an Event to be able to describe the other competitors, and the Picture with the OneToOne relationship with the Event should be the primary contestant. So once the primary contestant gets deleted, I also want to delete the Event. Unfortunately, I can't just add a ForeignKey relationship in Event or that would cause an error. So, is there someway to do this for a OneToOne relationship?
class Event(models.Model):
competitors = models.ManyToManyField('Picture',null=True,blank=True)
class Picture(models.Model):
competition = models.OneToOneField(Event)
Quoting django docs:
When Django deletes an object, by default it emulates the behavior of
the SQL constraint ON DELETE CASCADE -- in other words, any objects
which had foreign keys pointing at the object to be deleted will be
deleted along with it.
Then, this is the default behavior.
See on_delete options for further details:
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
The possible values for on_delete are found in django.db.models:
CASCADE: Cascade deletes; the default.
...
Suppose you have two models:
class ModelA(models.Model):
name = models.CharField(max_length=30)
class ModelB(models.Model):
abc = models.CharField(max_length=30)
model_a = models.ForeignKey(ModelA)
Now lets do some working example:
modelAObj = ModelA.objects.create(name='aamir')
modelBObj = ModelB.objects.create(abc='cde', model_a=modelAObj)
modelAObj.delete() # this will also delete the modelBObj

Should I use a seperate table instead of many to many field in Django

I needed to assign one or more categories to a list of submissions, I initially used a table with two foreign keys to accomplish this until I realized Django has a many-to-many field, however following the documentation I haven't been able to duplicate what I did with original table.
My question is : Is there a benefit to using many-to-many field instead of manually creating a relationship table? If better, are there any example on submitting and retrieving many-to-many fields with Django?
From the Django docs on Many-to-Many relationships:
When you're only dealing with simple many-to-many relationships such
as mixing and matching pizzas and toppings, a standard ManyToManyField
is all you need. However, sometimes you may need to associate data
with the relationship between two models.
In short: If you have a simple relationship a Many-To_Many field is better (creates and manages the extra table for you). If you need multiple extra details then create your own model with foreign keys. So it really depends on the situation.
Update :- Examples as requested:
From the docs:
class Person(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __unicode__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
You can see through this example that membership details (date_joined and invite_reason) are kept in addition to the many-to-many relationship.
However on a simplified example from the docs:
class Topping(models.Model):
ingredient = models.CharField(max_length=128)
class Pizza(models.Model):
name = models.CharField(max_length=128)
toppings = models.ManyToManyField(Topping)
There seems no need for any extra data and hence no extra model.
Update 2 :-
An example of how to remove the relationship.
In the first example i gave you have this extra model Membership you just delete the relationship and its details like a normal model.
for membership in Membership.objects.filter(person__pk=1)
membership.delete()
Viola! easy as pie.
For the second example you need to use .remove() (or .clear() to remove all):
apple = Toppings.objects.get(pk=4)
super_pizza = Pizza.objects.get(pk=12)
super_pizza.toppings.remove(apple)
super_pizza.save()
And that one is done too!