Why do I get a ForeignKey constraint violation - django

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.

Related

Setting default value of field in model to another model instance

Model
class SlackPermission(models.Model):
#fields
class GithubPermission(models.Model):
#fields
class Employee(models.Model):
#fields
slack_permission = models.OneToOneField(SlackPermission, on_delete=models.CASCADE, related_name='Slack',default=SlackPermission.objects.get(pk=1))
github_permission = models.OneToOneField(GithubPermission, on_delete=models.CASCADE, related_name='Github',default=GithubPermission.objects.get(pk=1))
Error:
ValueError: Cannot serialize: <GithubPermission: GithubPermission object (1)>
There are some values Django cannot serialize into migration files.
I am creating API just to create Employee. Where there is not option of giving slackpermissions and githubpermissions. How do I give default value in there?
The problem is that the default is calculated immediately, and for migrations, it can not really serialize that.
That bing said, it is not very useful to do this anyway. You can just pass the primary key as default value. This is specified in the documentation on the default=… parameter [Django-doc]:
For fields like ForeignKey that map to model instances, defaults should be the value of the field they reference (pk unless to_field is set) instead of model instances.
So we can write this as:
class Employee(models.Model):
full_name = models.CharField(max_length=100)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
slack_permission = models.OneToOneField(
SlackPermission,
on_delete=models.CASCADE,
related_name='Slack',
default=1
)
github_permission = models.OneToOneField(
GithubPermission,
on_delete=models.CASCADE,
related_name='Github',
default=1
)
Note that you should ensure that there exists an object with that primary key. Therefore it might not be ideal to do that.
The issue here is that you are attempting to set a field value to an object instance. So your default value should be just 1 if you are certain of the pk.
Also, I am not sure the advantage of creating two separate models for these permission values. Seems like they can just be fields in your employee model. Seems like these permissions share identical fields as well which will allow you to flatten them a bit.

How can I prevent the fields in inlines to reference the latest created object in Django Admin

I have a model PlacedOrder that is referenced by other models using OneToOneField and ForeignKey. So I am using StackedTabularInline to render it on my PlacedOrderAdmin model.
The weird behavior is that when I start my django application I can create a new PlacedOrder object with no problems but after that when I try to create another object the fields on the inlines are already filled with the content from the object that I just created and I can't create a new object no matter what I try, it keeps showing me the error "Please correct the errors below."
Only the fields that belongs to the inlines does that, the fields from the model PlacedOrder are "clean". If I restart django I can see all the objects created and their data seems correct.
part of the Models:
class PlacedOrder(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4(),
editable=False
)
...
total = models.DecimalField(
_('total'),
max_digits=8,
decimal_places=2
)
class OrderStatus(models.Model):
placed_order = models.OneToOneField(
PlacedOrder,
on_delete=models.CASCADE
)
status = models.CharField(
_('status'),
choices=STATUS_CHOICES,
max_length=30,
)
Here is one of the inlines:
class OrderStatusInline(nested_admin.NestedTabularInline):
model = app_models.OrderStatus
and the Order
#admin.register(app_models.PlacedOrder)
class OrderAdmin(nested_admin.NestedModelAdmin):
inlines = (OrderStatusInline, OrderPaymentInline, OrderDeliveryInline, SelectedProductInline, )
Fixed it
So I found out that the problem had nothing to do with what I posted here, it was actually how I was declaring the id inside PlacedOrder.
On my original model I had the default set as uuid.uuid4() but that was creating all the problem, once I changed it to uuid.uuid4 everything was fine.
I think the problem is that with a OneToOne relationship the actual field defining the relationship has to be on the inline model, not the parent one - in just the same way as for a ForeignKey
Take a look at this post for more info: Django Admin: OneToOne Relation as an Inline?

When splitting a Django model, How to keep ForeignKey and ManyToMany relationships during data migration?

I have a Django model that is doing way too much. Here's an abbreviated example of the model. Basically, it can represent four different Entity types, and there are recursive ForeignKey and ManyToMany relationships that point to other entities.
This project is currently using Django 1.8.x and Python 2.7.x, but I can upgrade those if the solution requires it.
class Entity(models.Model):
"""
Films, People, Companies, Terms & Techniques
"""
class Meta:
ordering = ['name']
verbose_name_plural = 'entities'
# Types:
FILM = 'FILM'
PERSON = 'PERS'
COMPANY = 'COMP'
TERM = 'TERM'
TYPE_CHOICES = (
(FILM, 'Film'),
(PERSON, 'Person'),
(COMPANY, 'Company'),
(TERM, 'Term/Technique'),
)
created = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
type = models.CharField(max_length=4, choices=TYPE_CHOICES, default=FILM)
slug = models.SlugField(blank=True, unique=True, help_text="Automatically generated")
name = models.CharField(max_length=256, blank=True)
redirect = models.ForeignKey('Entity', related_name='entity_redirect', blank=True, null=True, help_text="If this is an alias (see), set Redirect to the primary entry.")
cross_references = models.ManyToManyField('Entity', related_name='entity_cross_reference', blank=True, help_text="This is a 'see also' — 'see' should be performed with a redirect.")
[... and more fields, some of them type-specific]
I realize this is rather messy, and I'd like to remove 'type' and make an EntityBase class that abstracts out all of the common fields, and create new Film, Person, Company, and Term models that inherit from the EntityBase abstract base class.
Once I create the new models, I think I understand how to write the data migration to move all of the field data over to the new models (iterate over objects from Entity, filtered via type, create new objects in the appropriate new model)... except the ForeignKey and ManyToMany relationships. Maybe I'm thinking about this the wrong way, but how can I transfer those relationships when, during the migration, the new object that the relationship points to may not exist yet?
I suspect this may mean a multi-step migration, but I haven't quite worked out the right way to do it.
There is nothing magical about m2m and fk fields. This is the procedure that I would follow... It might be a bit blunt, but will get the job done:
Make a BACKKKUPPPPPPppp of the database!!
Make another backup!
Create the new model and migration
Write a new data migration that will manually iterate over existing models and update the new model, one-by-one. Don't be afraid of the for loop here, unless you have millions of entries in db.
Delete redundant models and/or fields, make migration for this.
Run those migrations :)
In practice, this means a lot of restoring from the "BACKKKUPPPPPPppp" until the migrations are just right.
One little thing to take care of:
M2m fields cannot get any value if model is not yet saved (because model gets its ID on first save). I would do something like, in the manual migration:
new_instance = NewModel()
new_instance.somefield = "whatever"
new_instance.meaning = 42
....
new_instance.save()
new_instance.that_m2m_field.add(some_related_obj)
Of course, make sure you read the docs in detail, especially that bit about importing the model class - you can't just import it from myapp.models import MyModel, instead do:
MyModel = apps.get_model("myapp", "MyModel")
One possible tripping stone might be the model inheritance that you plan to introduce. Generally, you will want to operate on the child model, and access the parent from there as / if needed. Parent can be accessed via the implicit ptr attribute - in your example it would be entitybase_ptr or something similar (that is just a OneToOne field). Going in the other direction, however, (from parent to unknown child) is not as straightforward, because parent doesn't a priori know what is the class of its child.

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 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