How exactly does CASCADE work with ManyToMany Fields in Django - django

Im Wondering how exactly does CASCADE work with ManyToMany Fields in Django.
A short example:
class Project(Model):
name = TextField(null=False)
class Job(Model):
projects = ManyToManyField(Project, on_delete=CASCADE, null=False)
name = TextField(null=False)
As you can see I have a ManyToManyField here. So basically a Project can have mutiple Jobs and a Job can belong to Multiple different Projects. What I want is that a Job is automatically deleted only when all Projects it belongs to are deleted. Does CASCADE work like that in this scenario?

CASCADE doesn't work like that. In your case, with CASCADE, with two projects A and B, a job J_DAILY linked to both projects, if you delete project A, then J_DAILY will be deleted too.
If you want your job to "live to its last project". You should change your on_delete to DO_NOTHING and add a check on deleting projects.
#receiver(pre_delete, sender=Project)
def delete_related_jobs(sender, instance, **kwargs):
for job in instance.job_set.all():
# No remaining projects
if not job.projects.exclude(id=instance.id).count():
job.delete()

Related

Shared models between two Django projects and ForeignKey to a model that exists only in one of them

I have two Django projects that communicate with each other. The first one contains model A and B that has a ForeignKey to A. The first project sends and receives serialized B objects from the second project. I want the second project to contain just B, but it needs the value of that ForeignKey. These models are defined as follows:
class A(models.Model):
...
class B(models.Model):
fk = models.ForeignKey(to='A', on_delete=models.PROTECT)
...
The problem is that ForeignKey to A in model B requires model A to be defined in the second project. Its objects also have to exist so that the database is consistent and there are no problems, e.g., in the admin panel.
In the end, I'd like to treat the fk field as a full-fledged ForeignKey in the first project and as some kind of read-only generic identifier in the second one. Specifically, I need to retain the functionality of querying both ways in the first project, e.g., fk__some_a_field and b_set. I would like to have the same code base for the model in both projects to ensure databases in the two projects stay synchronized. How can I achieve this in a clean way?
EDIT:
I was also considering fk = CustomField(...) which would be more or less defined as
if IS_FIRST_PROJECT:
CustomField = ForeignKey
else:
CustomField = IntegerField
but the issue is that I'd need a clean way to select the type of integer field that exactly matches the default foreign key. Also, I am not sure if such a solution could bring unexpected problems.
Specifically, I need to retain the functionality of querying both ways in the first project, e.g., fk__some_a_field and b_set.
If you want to use django orm, you would have to recreate your A model from project 1 in project 2. But as model A is managed by the project 1, consider adding next lines to your model in project 2:
class A(models.Model):
...
class Meta:
managed = False
db_table = 'your database table name where A model is stored'
managed=False would tell django to ignore migrations for it, and django won't be allowed to change that model's database table.
The other solution if you don't want to duplicate models from project 1 to project 2, is to not use django orm. And write sql queries by yourself. But as you mentioned you don't want to do this
P.S if you don't know how to see name of database table for model A, you can output it like this: a_model_instance._meta.db_table, or look it in some tools like pgadming if you are using postgres

Combined forms in Django for attributes of parent model

Having a lot of models, I have started to factor out common blocks with the aim of my database reaching second normal form. As the application should be used by a sales team, most entries are some kind of orders. An excerpt from my models file looks like this:
class Order(models.Model):
dl = models.CharField(max_length=100)
cl = models.CharField(max_length=100)
(...)
class Setup(models.Model):
order = models.ForeignKey(Order) # could be OneToOneField()
name = models.CharField(max_length=200)
package = models.CharField(choices=(
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
('XL', 'Extra large'),
('C', 'Custom')
), max_length=2)
server = models.ForeignKey(Webserver)
(...)
While it does not make any logical sense to keep the order details out of the model for a setup order, it helps to keep the project maintainable, since setup orders are not the only things coming in and this way you can change the order model and all other models/orders are updated too.
Here comes the problem. For attributes like Setup.server Django's default behaviour of creating a dropdown with all web servers the company can offer is totally fine. If the team decides to add another one, it can simply create another server option on another page. But for order, I would like that the OrderForm is included on the same page as the SetupForm, ideally as a separate Fieldset. After submitting the form, the new order should be added to the database and Setup.order is to be filled. I know that I can code it for this special case, but the application will contain numerous forms, so a generic solution would be better.
A possible solution could be to create a custom models.ForeignKey or models.OneToOneField with a custom option and a generic view that than renders a template with two forms and links the objects afterwards.
Did anyone have a similar problem? Does anyone know a simple, maybe even builtin solution to that?
EDIT 1
I have thought about inline formsets which were the solution to a similar question which used this example
class Contact(models.Model):
...
class Communication(models.Model):
contact = models.ForeignKey(Contact)
and by using a communication fieldset. However, this treats the class containing as the child, which is not the case in my example. But it still is an option, even if it would still have to be automated so it can be used quickly for all links between the other models.

Is it possible to duplicate a django-mptt model?

Let's say I have a django-mptt model that looks like this:
class Category(MPTTModel):
name = models.CharField(max_length=50)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
These Categories (and sub-categories) will serve as a kind of template for the categories used in a project. When a user starts a new project, the user will select which Categores, Sub-Categories, etc. to use. The user should be able to also add/edit Categories. The thing is that they need to be specific the project so that when another project is created, the user will start with the original/default categories.
Is there any way to duplicate the MPTTModel/database table(s) to create project specific one where categories can be edited/added without it affecting the default ones?
I can think of one way to solve this issue which would be to add something like
projects = models.ManyToManyField(Project)
and create a default/template project.
What's the best approach here?
You have the right idea having a "projects" field to match a category with various projects. You might be better off using a "project" field with a ForeignKey relationship:
project = models.ForeignKey(project)
Then you could duplicate the Category, creating a new database row, for each new project. That way if someone working on project foo changes the Category bar it doesn't affect project baz which could also be using Category bar. Both Category bar entries will have different primary keys in the database.
You could also have a project called "default", All the "default" Categories can get duplicated whenever a user creates a new project.
Of course you'll need code to display only Categories from the project your user is working on and to duplicate the Categories when a new project is created.

Migrating from abstract model to proxy models

Right now, I have an abstract model with several models that inherits its fields. But I have discovered the power of Proxy models, and I want to implement them to my app. This is a picture from now:
class BaseModel(models.Model):
field_1 = models.CharField(max_length=10)
field_2 = models.CharField(max_length=10)
field_3 = models.CharField(max_length=10)
class Meta:
abstract = True
class Model1(BaseModel):
pass
def __unicode__(self):
return self.field_1
class Model2(BaseModel):
pass
def __unicode__(self):
return self.field_1
And this is what I want:
class BaseModel(models.Model):
field_1 = models.CharField(max_length=10)
field_2 = models.CharField(max_length=10)
field_3 = models.CharField(max_length=10)
class Model1(BaseModel):
pass
class Meta:
proxy = True
def __unicode__(self):
return self.field_1
class Model2(BaseModel):
pass
class Meta:
proxy = True
def __unicode__(self):
return self.field_1
The problem is when I remove the "abstract = True" sentence. When I try to migrate, this is the warning:
You are trying to add a non-nullable field 'basemodel_ptr' to Model1 without a default; we can't do that (the database needs something to populate existing rows).
Ok, got it. I read "ptr" is a pointer to the parent model, which is BaseModel, but I don't have any BaseModel, and I cannot get it until I migrate. How can I fix this??
The migrations for this are not trivial.
Currently, your database has two tables:
yourapp_model1
yourapp_model2
Both have the same columns but they use different sequences meaning that their primary keys clash: Both of the tables will start counting their IDs (aka PKs) from 1 onwards. There is a very good chance that there is an instance of Model1 with pk=1 as well as an instance of Model2 with pk=1 and they are not the same.
This is the point of having an abstract model and concrete implementations: share Django code (business logic) while separating the data in the DB. For example, because it is semantically different (different types).
The point of proxy models is the exact opposite: while the data is located in one table in the DB, the proxy models allow you to implement different behaviour but based on the same DB data.
If you are now migrating from abstract models to proxy models it means that what you once considered different types will now become the same types (from a database point of view). As mentioned at the beginning, you will have to move the data from several tables into one and regenerate the IDs for at least part of it. Meaning also, that part of the resources that are currently using these URLs will change/cease to exist/point to a different resource.
Unless you can start from scratch (no live data that you have to support) you should really appreciate the magnitude of this change.
Should you not have the need to support live data:
drop the database
recreate the database
remove all migration files
recreate all migration files from scratch
call migrate
Data migration of live data:
Note that there are other ways to do this. Search for "Django data migration".
create the new Model structure with new names (no collisions)
makemigrations which will now pick up the new model structure and create new and empty tables in database leaving the old tables and their data untouched
create a management command that reads in the old model instances and copies them into the new tables
once this command has been run on production you can deprecate the old models and remove them while making sure that everything that was depending on them is now using the new tables

Django how to get specific field with complex join in one-to-many relationship

I have a database consists of 3 tables
Project table
Section table
Task table
And the their relationships
Project table have one-to-many relation ship with Section table.
Section table have one-to-many relation ship with task table.
I have already queried a target task record by this code
task = tasks.objects.filter(section_id=sectionID)
Assume that I have sectionID and then I would like to get project.id from a task object, what should I do?
Thank you.
I guess you models look like that:
class Project(models.Model):
#...
class Section(models.Model):
#...
project = models.ForeignKey(Project)
class Task(models.Model):
#...
section = models.ForeignKey(Section)
So in order to get a task project you just have to follow the relation.
#when task is a single task object
project = task.section.project
Have in mind that this will make 2 queries (one to get the section and one to get the project) you can optimize this with select_related
tasks = Task.objects.filter(section_id=sectionID).select_related('section__project')
for task in tasks:
project = task.section.project
print project.id
ending with a single query.
I guess you can also want to know how to get a queryset of projects contained by some sectionID, in this case you can use the reverse relations like so:
projects = Project.objects.filter(section__id=sectionID)
You can reach it using task.section.project.id.