Django Relating 2 Models - django

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

Related

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)

Why do I get a ForeignKey constraint violation

As can be seen below, I have 2 models connected via an intermediary model to form a ManyToMany relationship. The problem is that, when I delete a Tender object in get this error.
update or delete on table "tender_details_tender" violates foreign key constraint "user_account_company_tender_id_984ea78c_fk_tender_de" on table "user_account_companyprofile_assignedTenders"
DETAIL: Key (id)=(1) is still referenced from table "user_account_companyprofile_assignedTenders".
I thought by adding on_delete=models.CASCADE in the ForeighKeys (i.e. in the intermediary model) would solve this problem, but apparently not.
class CompanyProfile(models.Model):
assignedTenders = models.ManyToManyField(Tender, through='UsersTenders', related_name='UserCompanies')
# connects users to the tenders they match.
class UsersTenders(models.Model):
user = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE, related_name='userTenderLink')
tender = models.ForeignKey(Tender, on_delete=models.CASCADE, related_name='userTenderLink')
sent = models.BooleanField(default=False, blank=False)
class Meta:
unique_together = ("user", "tender")
class Tender(models.Model):
tenderCategory = models.ManyToManyField(Category, blank=False) #this field holds the tender category, e.g. construction, engineering, human resources etc.
tenderProvince = models.ManyToManyField(Province, blank=False) #this is the province the tender was a
For what its worth, I know what is causing this problem, what I don't know is how to fix it. The problem is that initially I had the ManyToManyField under the CompanyProfile model without the "through" argument, so as you might imagine Django created it's own intermediary table which is "user_account_companyprofile_assignedTenders" as shown in the error. I later decided to create my own intermediary model (i.e. UsersTenders) because I wanted an extra field there, so I had to add the "through" argument in my ManyToManyField (i.e. 'assignedTenders'). That worked fine but the old intermediary model "user_account_companyprofile_assignedTenders" did not get deleted automatically, I assume its because a few relationship had be created before the change. How can I delete "user_account_companyprofile_assignedTenders" without destabilizing my project.
Did you add on_delete after database migration? If so have you made a migration after adding on_delete?
You could try to set null=True to all fields and then try ti figure out which foreign key is causing problems.
bdw. when you set blank=True that only means that your form fields will not insist on this fields to be filled for submitting.

Using OneToOneField with multiple versions of data

I want to have two versions of the same model while still benefiting from OneToOneField's reverse relation.
For example, let's say that I have the following models:
class Company(models.Model):
exists = models.BooleanField()
class ExtraInforation(models.Model):
company = models.OneToOneField(Company)
wealthy = models.BooleanField()
At this point my code uses the brilliance of the OneToOneField reverse relation, doing company.extrainformationcalls all over the place.
Then I get a new requirement: we can't trust the ExtraInformation without verifying it first! Pfft, any company could claim that it's wealty...
Any changes to ExtraInformation need to be confirmed before publishing. Let's say that the company isn't wealthy when it registers and that information gets confirmed. Later the company wants to mark itself wealthy. At that point there needs to be the confirmed/public version of ExtraInformation and the unconfirmed version that needs to be confirmed.
I want to be able to still keep those handy OneToOneField reverse relation calls but also have another version of the same data. The problem is, of course, that there can be only one row with reference to this company in the OneToOneField.
Currently my solution is to create a new table:
class ExtraInforationUnconfirmed(models.Model):
company = models.OneToOneField(Company)
wealthy = models.BooleanField()
Once the information is confirmed, the fields are copied from ExtraInforationUnconfirmed to ExtraInformation. This solution isn't very DRY or clean.
What would be the best way to solve this issue?
I studied proxy models and model inheritance. The best alternative way I could think of is to have a base model and inherit two models that have both have OneToOneField relation of their own to Company.
Add a boolean feild to the model and change it to true when confirmed:
class ExtraInforation(models.Model):
company = models.OneToOneField(Company)
wealthy = models.BooleanField()
confirmed = models.BooleanField(default=False)
UPDATE
Based on your comment I suggest a version filed which can be a simple integer or a datetime. I would avoid creating two models at any cost :)
class ExtraInforation(models.Model):
company = models.ForeignKey(Company, related_name='extrainformations')
wealthy = models.BooleanField()
# version = models.PositiveIntegerField()
version = models.DateTimeField(auto_now_add=True)
confirmed = models.BooleanField(default=False)
You can add a property to Company that returns the last extrainformation so that company.extrainformation will still work:
#property
def extrainformation(self):
return self.extrainformations.order_by("-version").first()

Django-admin set foreign key to Null for inlines selected item instead of delete

I Have a model AB that holds two foreign keys A_id and B_id.
class AB(models.Model):
A_id = models.ForeignKey('A')
B_id = models.ForeignKey('B')
field_1 = models.CharField(max_length=200, blank=True)
field_2 = models.CharField(max_length=200, blank=True)
When editing A or B, AB items are edited inlines, what I want to achieve is that when editing let's say B I want to keep the selected AB items and set the foreign key B_id to null instead of deleting them.
thanks for any hint
I wound up here because I had the same question. I think the previous answer misses the issue here -- the use case here is the user checks the "delete" checkbox on an InlineModelAdmin, not that they delete the model linked by the foreign key.
I think you can simplify the original problem, consider just that model B has a nullable foreign key to model A:
class A(models.Model):
pass
class B(models.Model):
linked_a = models.ForeignKey(A, null=True)
Then the admin lists each B linked to an A using an inline:
class BInline(TabularInline):
model = B
class AModelAdmin(ModelAdmin):
inlines = [BInline]
The question is, is there a way to make the "delete" checkbox on BInline result in B.linked_a = None rather than deleting the instance of B?
The reason this seems like a logical operation is that if you used a ManyToManyField to join these two objects, that's what would happen -- it wouldn't delete B, it would just "unlink" it.
Unfortunately, the answer as far as I can tell is that you can't do this easily. In both cases the inline is showing a database row, but while the inline for a ForeignKey is showing the related object itself, the inline for a ManyToManyField is showing a row from the join table (eg. the relationship). So in terms of database operations the "delete" action is the same, it's just that in one case you delete the related object, in the other case you just delete the relationship.
If I understand this correctly, what you want is protection against cascade deletion.
If this is the case, you need to specify what django should do on deletion of an A or B model.
From the docs:
When an object referenced by a ForeignKey is deleted, Django by default emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey. This behavior can be overridden by specifying the on_delete argument. For example, if you have a nullable ForeignKey and you want it to be set null when the referenced object is deleted:
In order to set the ForeignKey null, you can do it like this:
A_id = models.ForeignKey('A', null=True, on_delete=models.SET_NULL)
B_id = models.ForeignKey('B', null=True, on_delete=models.SET_NULL)
Good luck and hope this helps.
You can use a custom inline form set and override the delete_existing method which is available in django 1.11+.
from django.forms.models import BaseInlineFormSet
from django.db import models
from django.contrib import admin
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(Publisher, null=True)
class CustomInlineFormSet(BaseInlineFormSet):
def delete_existing(self, obj, commit=True):
"""Unhook a model instead of deleting it."""
if commit:
obj.publisher = None
obj.save()
class BooktInline(admin.TabularInline):
formset = CustomInlineFormSet
This changes it so that the 'delete' action on admin inline formsets will unhook the inline model instead of deleting it.

Django: remove all m2m relations

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