Django Bi-directional ManyToMany - How to prevent table creation on second model? - python

I have two models, each has a shared ManyToMany, using the db_table field. But how do I prevent syncdb from attempting to create the shared table, for the second model?
class Model1(models.Model):
othermodels = ManyToManyField('Model2', db_table='model1_model2', related_name='model1_model2')
class Model2(models.model):
othermodels = ManyToManyField('Model1', db_table='model1_model2', related_name='model2_model1')
It's working great in my dev environment, because some of the tables got created piecemeal, as I built it all out. But from an empty database, syncdb throws:
_mysql_exceptions.OperationalError: (1050, "Table 'model1_model2' already exists")
Is there a flag that I'm missing from the second model's field to prevent duplicate table creation? Or am I just doing this entirely wrong?

I also found this solution, which worked perfectly for me :
class Test1(models.Model):
tests2 = models.ManyToManyField('Test2', blank=True)
class Test2(models.Model):
tests1 = models.ManyToManyField('Test1', through=Test1.tests2.through, blank=True)

You don't need to put a ManyToManyField on both sides of the relation. Django will do that for you.
You probably want something more like this:
class Model1(models.Model):
name = models.CharField(max_length=128)
...
class Model2(models.Model):
name = models.CharField(max_length=128)
othermodels = models.ManyToManyField(Model1, through='Model1Model2')
...
class Membership(models.Model):
class Meta:
db_table = 'model1_model2'
model1 = models.ForeignKey(Model1)
model2 = models.ForeignKey(Model2)
When you're working with your models, an instance of Model1 will have a othermodels_set field which is automatically added by django. Instances of Model2 will have othermodels.

class Awesome(models.Model):
one = models.TextField()
class Meta:
# Prevent table creation.
abstract = True
http://docs.djangoproject.com/en/dev/topics/db/models/#abstract-base-classes
It's not what you're looking for, but it's the closest they have I belive. Would it not be simpler to make a view?
Maybe:
class Awesome(models.Model):
one = models.CharField(max_length = 255)
two = models.CharField(max_length = 255)
class AwesomeOne(Awesome):
fieldOne = models.ForeignKey(User, related_name = 'one')
class Meta:
abstract = True
class AwesomeTwo(Awesome):
fieldTwo = models.ForeignKey(User, related_name = 'two')
class Meta:
abstract = True
Then, you can have one table created and override the __getattr__ to block access to the original fields.

In Django ManyToMany are bi-directional by default. The think is that you only have to define it on one model, not on both (and usually you don't need to give a name to the table):
class Model1(models.Model):
othermodels = ManyToManyField('Model2')
class Model2(models.model):
...
That's it. Now syncdb will be happy. For more info: Django docs
The only drawback is, if you use the admin, you will have access to othermodels only in Model1.
Edit
So, if you want to have access to the ManyToMany in both models in the Admin, currently the official solution is to use inlinemodel for the second model. I had also this same problem/need just a few days ago. And I was not really satisfied with the inlinemodel solution (heavy in DB queries if you have a lot of entries, cannot use the *filter_horizontal* widget, etc.).
The solution I found (that's working with Django 1.2+ and syncdb) is this:
class Model1(models.Model):
othermodels = models.ManyToManyField('Model2', through='Model1Model2')
class Model2(models.Model):
othermodels = models.ManyToManyField('Model1', through='Model1Model2')
class Model1Model2(models.Model):
model1 = models.ForeignKey(Model1)
model2 = models.ForeignKey(Model2)
class Meta:
db_table = 'app_model1_model2'
auto_created = Model1
See ticket 897 for more info.
Unfortunately, if you're using South you will have to remove the creation of the app_model1_model2 table in every migration file created automatically.

Related

How to override M2M field names and models in Django with an existing database?

I'm using Django 3.1.3 and working with an existing postgresql database. Most of the models and fields names of this DB are badly chosen and/or way too long. Most of the time its easy to change them with some handy Django options like so :
class NewModelName(models.Models):
new_field_name = models.CharField(max_length=50, db_column='old_field_name')
class Meta:
managed=False
db_table='database_old_table_name'
But let say I want to change a M2M field name and the corresponding model name. I'd like to have something like :
class Foo(models.Models):
new_m2m_field_name = models.ManyToManyField('RelatedModel', blank=True, db_column='old_m2m_field_name')
class Meta:
managed=False
db_table='foo_old_table_name'
class RelatedModel(models.Models):
name = models.CharField(max_length=50)
class Meta:
managed=False
db_table='related_model_old_table_name'
But if I do that, Django will throw an error stating
django.db.utils.ProgrammingError: relation "foo_new_m2m_field_name" does not exist. It is like it is ignoring the db_column option. Any idea how I could get to a similar result ?
Thanks!
From Django documentation regarding ManyToManyField
ManyToManyField.db_table The name of the table to create for storing
the many-to-many data. If this is not provided, Django will assume a
default name based upon the names of: the table for the model defining
the relationship and the name of the field itself.
Also depending on column names (non standard names) in original database you might have to define through model ( pivot table) as through table
You will probably need to manually define the Through model (that Django would otherwise implicitly create behind the scenes) in order to make it unmanaged.
class Foo(models.Models):
new_m2m_field_name = models.ManyToManyField(
"RelatedModel",
blank=True,
db_column="old_m2m_field_name",
through="FooRelatedJoin", # <- new
)
class Meta:
managed = False
db_table = "foo_old_table_name"
class RelatedModel(models.Models):
name = models.CharField(max_length=50)
class Meta:
managed = False
db_table = "related_model_old_table_name"
class FooRelatedJoin(models.Models): # <- all new
foo = models.ForeignKey(Foo)
related_model = models.ForeignKey(RelatedModel)
class Meta:
managed = False
db_table = "foo_join_table"
You could add a property db_table (link)
linking to the previous table, named foo_old_table_name in your case.
According to the doc,
By default, this table name is generated using the name of the
many-to-many field and the name of the table for the model that
contains it
So for the field new_m2m_field_name, the previous table making the link was named : old_field_name_database_old_table_name.
Hence :
new_field_name = models.CharField(max_length=50, db_column='old_field_name', db_table='old_field_name_database_old_table_name')
The option through could be changed too, but I do not think it is necessary if the modifications on names are coherent.

Django edit parent's fields inside child

I'm facing a problem with django-admin. I have three objects:
Description
Job
Project
And I want to edit the Description directly inside Job and Project.
Here is my model.py:
class Description(models.Model):
short_desc = models.TextField()
long_desc = models.TextField()
class Job(models.Model):
location = models.TextField()
desc = models.ForeignKey(Description)
class Project(models.Model):
name = models.TextField()
desc = models.ForeignKey(Description)
So, conceptually, Description is the parent of Job and Project.
And my admin.py:
class DescriptionInLine(admin.StackedInline):
model = Description
#admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
model = Project
inlines = [DescriptionInLine]
#admin.register(Job)
class JobAdmin(admin.ModelAdmin):
model = Job
inlines = [DescriptionInLine]
Whenever I run the django server, I get the following error:
<class 'admin.DescriptionInLine'>: (admin.E202) 'Description' has no ForeignKey to 'Job'.
I understand why I get the error: django expects the relation to be in the other way.
I also tried replacing ForeignKey by OneToOneField, without any success.
Any idea on how to solve this?
You get this error because inlines are intended to be used in the other direction (See this question).
I think for your usecase you'd better use model inheritance:
class Description(models.Model)
class Meta:
abstract = True
# Abstract is optional but I think for your usecase,
# standalone `Description` does not make any sense.
# If not `abstract`, a one-to-one relation will be implied
# between parent and children
short_desc = models.TextField()
long_desc = models.TextField()
class Job(Description):
location = models.TextField()
class Project(Description):
name = models.TextField()

Dynamically creating the related_name from META class

Most of my Django models use the same User Mixin, because of this I would like to dynamically create the related_name for the field.
I would like it to be the class name where TestModel becomes test_models or maybe even a set name from the meta class on the main model.
I have looked at self__class__.__name__ but this give me the name of the User class.
Would it be possible to do something like below, if so how....
class User(models.Model):
user = models.ForeignKey(USER, related_name=META.related_name)
class Meta:
abstract = True
class TestModel(User):
title = models.CharField(max_length=80)
class Meta:
related_name = "test_model"
I think it might be sufficient to handle this like it is documented here.
# myapp/models.py
class User(models.Model):
user = models.ForeignKey(
USER,
related_name="%(app_label)s_%(class)s_related"
)
class Meta:
abstract = True
class TestModel(User):
title = models.CharField(max_length=80)
This way the related name would dynamically become myapp_testmodel_related. Of course you can tweak the name and simplify the pattern, if it is certain that the names can't clash between multiple apps.

If I use Multi Table Inheritance for a set of related classes can they have separate admins?

I need to choose between Multi Table Inheritance and ABC Inheritance and I am not sure if I can still have separate admins for each subclass. I need all the base class fields and all the fields from the subclass in each subclass admin screen.
I am sorry if this is a stupid question, I am still not even finished with the manual but I have a deadline.
Of course, you can have an admin screen for each of your models, and all the fields from base model would be present on child models.
from django docs:
each model in the hierarchy is a model all by itself
Then, if you have:
models.py
class Service(models.Model):
owner = models.ForeignKey('auth.User')
name = models.CharField(max_length=128)
class VariationAService(Service):
# fields
class VariationBService(Service):
# more fields
You can do something like this:
admin.py
class ServiceAdmin(admin.ModelAdmin):
[...]
class VariationAServiceAdmin(admin.ModelAdmin):
[...]
class VariationBServiceAdmin(admin.ModelAdmin):
[...]
You'll get all fields either way. The difference between the two is that with multi-table inheritance...
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
...you'll get both a Place object and Restaurant object, which can both be edited in the admin, but with abstract base classes...
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Meta:
abstract = True
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
...you'll only get a Restaurant object, which can be edited in the admin.

Use a class before its definition in Django model

When I try to syncdb I get the error Menu is not a valid class Name.
How can I resolve that relationship case :
class MenuItem(model.Models)
title = models.CharField(max_length=200)
submenus = models.ManyToManyField(Menu, blank=True, null=True)
class Menu(Container):
links = models.ManyToManyField(MenuItem)
From the Django book:
If you need to create a relationship on a model that has not yet been
defined, you can use the name of the model, rather than the model
object itself:
E.g.:
class MenuItem(model.Models)
title = models.CharField(max_length=200)
submenus = models.ManyToManyField('Menu', blank=True, null=True)
^ ^
Edit:
As Francis mentions (and as is written in the documentation):
It doesn't matter which model has the ManyToManyField, but you should only put it in one of the models -- not both.
One of these models has a many to many, the other one uses Django's reverse relations (https://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward)
So how I would set it up:
class Menu(Container):
links = models.ManyToManyField(MenuItem)
class MenuItem(model.Models)
title = models.CharField(max_length=200)
Then when I wanted a MenuItem's Menus:
menu_item_instance.menu_set.all()

Categories