I have an abstract object called parameter. parameter can be of several different types. For example - Numeric Parameter, Constant parameter, MultiValue parameter etc.
Each parameter can be related to many parameter of different types and vise versa.
After reviewing Django's doc on Model inheritance I decided what I need is a simple abstract base class. An example of a many to many relation from the base class can be found later on in the docs.
class ParameterBase(models.Model):
id = models.AutoField(primary_key=True)
description = models.CharField(max_length=200)
sort_order = models.DecimalField(null=False, max_digits=6, decimal_places=4)
m2m = models.ManyToManyField('self',related_name='dependent_on')
class Meta:
abstract = True
class ParameterConstant(ParameterBase):
value = models.DecimalField(null=False, blank=False, max_digits=20 , decimal_places=4)
class ParameterNumeric(ParameterBase):
minimum = models.DecimalField(null=True, blank=True, max_digits=20 , decimal_places=4)
maximum = models.DecimalField(null=True, blank=True, max_digits=20 , decimal_places=4)
So after syncing i could see that django created 4 tables -
CREATE TABLE "calc_parameterconstant_m2m" (
"id" serial NOT NULL PRIMARY KEY,
"from_parameterconstant_id" integer NOT NULL,
"to_parameterconstant_id" integer NOT NULL,
UNIQUE ("from_parameterconstant_id", "to_parameterconstant_id")
)
;
CREATE TABLE "calc_parameterconstant" (
"id" serial NOT NULL PRIMARY KEY,
"description" varchar(200) NOT NULL,
"sort_order" numeric(6, 4) NOT NULL,
"value" numeric(20, 4) NOT NULL
)
;
ALTER TABLE "calc_parameterconstant_m2m" ADD CONSTRAINT "from_parameterconstant_id_refs_id_f893bb67" FOREIGN KEY ("from_parameterconstant_id") REFERENCES "calc_parameterconstant" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "calc_parameterconstant_m2m" ADD CONSTRAINT "to_parameterconstant_id_refs_id_f893bb67" FOREIGN KEY ("to_parameterconstant_id") REFERENCES "calc_parameterconstant" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE TABLE "calc_parameternumeric_m2m" (
"id" serial NOT NULL PRIMARY KEY,
"from_parameternumeric_id" integer NOT NULL,
"to_parameternumeric_id" integer NOT NULL,
UNIQUE ("from_parameternumeric_id", "to_parameternumeric_id")
)
;
CREATE TABLE "calc_parameternumeric" (
"id" serial NOT NULL PRIMARY KEY,
"description" varchar(200) NOT NULL,
"sort_order" numeric(6, 4) NOT NULL,
"minimum" numeric(20, 4),
"maximum" numeric(20, 4)
)
Now, This is obviously was not my intention - I want to be able to connect a parameter of each type to parameters of other types as well. Is there any way to achieve this goal using Django ORM and model inheritance ?
If the base parameter model was a table on it's own with Many to Many relation to it self and the sub tables were connected with an unbinding one to one relation this might be a good solution here database wise.
I think multi-table inheritance will solve your problem. To use it, just remove abstract = True from the ParameterBase.Meta subclass (or remove that subclass completely).
When using multi-table inheritance, to access attributes of a particular subclass, Django has to know that you're dealing with that subclass.
For example, this will fail:
p = ParameterBase.objects.get(...) # get a ParameterBase that is a ParameterConstant
print p.value
Instead, you would have to do this:
p = ParameterConstant.objects.get(...)
print p.value
Or this:
p = ParameterBase.objects.get(...) # get a ParameterBase that is a ParameterConstant
print p.paramaterconstant.value
One way to solve this would be to replace
m2m = models.ManyToManyField('self',related_name='dependent_on')
with
m2m = models.ManyToManyField('ParameterBase', related_name='dependent_on')
but Django won't let you create an m2m field that points to a model that hasn't been installed yet or one that is abstract. At this point ParameterBase is both of those.
I'd do it like this
class Parameter(models.Model):
id = models.AutoField(primary_key=True)
description = models.CharField(max_length=200)
sort_order = models.DecimalField(null=False, max_digits=6, decimal_places=4)
class ParameterType(models.Model):
parameter = models.ForeignKey(Parameter)
related_parameters = models.ManyToManyField(Parameter,related_name='dependent_on')
class Meta:
abstract = True
class ParameterConstant(ParameterType):
value = models.DecimalField(null=False, blank=False, max_digits=20 , decimal_places=4)
class ParameterNumeric(ParameterType):
minimum = models.DecimalField(null=True, blank=True, max_digits=20 , decimal_places=4)
maximum = models.DecimalField(null=True, blank=True, max_digits=20 , decimal_places=4)
If you use a ManyToMany field in an abstract parent class, you should provide a unique related_name as stated in the docs... in your case, this should go fine:
m2m = models.ManyToManyField('self',related_name='%(app_label)s_%(class)s_m2m')
Related
How to remove relation from model with ManyToMany field?
I've got a model with ManyToManyField relation. I need to remove relation but not data from the following model:
class TxHomes(models.Model):
user = models.ManyToManyField(settings.AUTH_USER_MODEL)
home_id = models.PositiveIntegerField(primary_key=True, unique=True, null=False)
name = models.CharField(max_length=255, null=True)
geo_name = models.CharField(max_length=255, null=True)
payload = models.JSONField()
Django ORM got tables generated:
-- auto-generated definition
create table main_txhomes
(
home_id integer unsigned not null primary key,
name varchar(255),
geo_name varchar(255),
...
);
create table main_txhomes_user
(
id primary key autoincrement,
txhomes_id ...,
user_id ...
);
When I apply to do that with a following code
TxHomes.objects.filter(
home_id__in=TxHomes.objects.filter(user=USER_ID).values('home_id')
,user=USER_ID).delete()
i got entire data deleted from main_txhomes
I want to keep data in main_txhomes table, what i need is to delete relations from main_txhomes_user table. How to do that?
Solution is found:
User.objects.get(id=USER_ID).txhomes_set.clear()
This is how we remove all relation to txhomes table for the user
I have recently started using django to read out oracle database. I am having a problem with created a model with foreign key.
Here is what I have in Oracle schema:
CREATE TABLE MTRS_CHANNELS(ID int NOT NULL PRIMARY KEY, ALIAS VARCHAR(255) NOT NULL UNIQUE, BOARDNUMBER int, CHIPNUMBER int, CHANNELNUMBER int, TYPEID int, SYSTEMNAME VARCHAR(255));
ALTER TABLE MTRS_CHANNELS ADD CONSTRAINT uniquesensornaming UNIQUE (BOARDNUMBER , CHIPNUMBER , CHANNELNUMBER, SYSTEMNAME);
CREATE TABLE MTRS_DATA(CHANNELID int NOT NULL, FLAG int, UPDATETIME TIMESTAMP, VALUE FLOAT);
ALTER TABLE MTRS_DATA ADD CONSTRAINT CHANNELIDENTIFIER FOREIGN KEY (CHANNEL_ID) REFERENCES MTRS_CHANNELS(ID);
Basically there are 2 tables MTRS_CHANNELS which stores channels identified by unique channelid and MTRS_DATA stores value for a channel, which is referenced by channelid as foreign key.
In my models.py I have :
from django.db import models
class Channel(models.Model):
id = models.IntegerField(primary_key=True)
alias = models.CharField(max_length=256)
boardnumber = models.IntegerField()
chipnumber = models.IntegerField()
channelnumber = models.IntegerField()
typeid = models.IntegerField()
systemname = models.CharField(max_length=255)
class Meta:
db_table = "MTRS_CHANNELS"
managed = False
class values(models.Model):
channel = models.ForeignKey(Channel,on_delete=models.CASCADE)
updatetime = models.DateTimeField()
value = models.FloatField()
class Meta:
db_table = "MTRS_DATA"
managed = False
The problem is that I am trying to load data in view it complains with:
ORA-00904: "MTRS_DATA"."ID": invalid identifier
When I try to modify channel to channel_id in values, I get
ORA-00904: "MTRS_DATA"."CHANNEL_ID_ID": invalid identifier
I might be missing something really obvious but the problem clearly comes from the way Foreign Key is declared.
Thanks and regards,
Ivan
I have been struggling with grasping relations for some time and would be very grateful if someone can help me out on this issue.
I have a relation that connects the User model to a ProcessInfo model via one to many and then I have a relation that connects the ProcessInfo to the ProcessAssumptions as One to one
Is there a way to use the User id to get all ProcessAssumptions related to all processes from that user.
I would like to retrieve a queryset of all ProcessAssumptions related to a user id
Here is the model relation :
class ProcessInfo(models.Model):
process_name = models.CharField(max_length=120, null=True)
user_rel = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
class ProcessAssumptions(models.Model):
completion_time = models.FloatField(default='0')
process_rel_process = models.OneToOneField(ProcessInfo, primary_key = True, on_delete=models.CASCADE)
Using field referencing for foreign keys.
process_assumption_objects = ProcessAssumptions.objects.filter(process_rel_process__user_rel=<user_id>)
Replace <user_id> with the id you wish to query for.
When you define a relationship to model X in another model Y, all related Ys can be accessed from an instance of X by X_instance.Y_set.all(). You can even perform the regular filter or get operations on that. X_instance.Y_set is the default object manager for Y (same as Y.objects), but it's filtered to only contain the objects that are related to X_instance.
So in this specific case, you can get all ProcessInfo objects for a certain user like this:
user = User.objects.get(the_user_id)
required_assumptions = [proc_info.process_assumptions for proc_info in user.process_info_set.all()]
This might be a bit hard to read with _set suffix, so you can define a related_name argument while defining the relation on the model.
like:
# in class ProcessInfo
user_rel = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, related_name='processes')
# and now you can do
some_user.processes.all()
I am hooking up to an existing Postgres database with Django, using inspectdb to generate my models.py file.
The tables in the Postgres were created like this:
CREATE TABLE "a"
(
"id" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
CONSTRAINT "PK.a" PRIMARY KEY ("id")
)
CREATE TABLE "c"
(
"id" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
CONSTRAINT "PK.c" PRIMARY KEY ("id")
)
CREATE TABLE "b"
(
"aid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
"cid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
CONSTRAINT "PK.b" PRIMARY KEY ("aid","cid"),
CONSTRAINT "FK_a" FOREIGN KEY ("aid")
REFERENCES "a" ("id") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT "FK_c" FOREIGN KEY ("cid")
REFERENCES "c" ("id") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE
)
I then run python manage.py inspectdb --database primary > models_test.py, which results in the following models_test.py.
class A(models.Model):
id = models.UUIDField(primary_key=True)
class Meta:
managed = False
db_table = 'a'
class B(models.Model):
aid = models.OneToOneField(A, models.DO_NOTHING, db_column='aid', primary_key=True)
cid = models.ForeignKey('C', models.DO_NOTHING, db_column='cid')
class Meta:
managed = False
db_table = 'b'
unique_together = (('aid', 'cid'),)
class C(models.Model):
id = models.UUIDField(primary_key=True)
class Meta:
managed = False
db_table = 'c'
note the OneToOneField defined on aid.
If I instead create table b as:
CREATE TABLE "b"
(
"aid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
"cid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
CONSTRAINT "PK.b" PRIMARY KEY ("cid","aid"),
CONSTRAINT "FK_a" FOREIGN KEY ("aid")
REFERENCES "a" ("id") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT "FK_c" FOREIGN KEY ("cid")
REFERENCES "c" ("id") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE
)
then rerun inspectdb I get the following output:
class A(models.Model):
id = models.UUIDField(primary_key=True)
class Meta:
managed = False
db_table = 'a'
class B(models.Model):
aid = models.ForeignKey(A, models.DO_NOTHING, db_column='aid')
cid = models.OneToOneField('C', models.DO_NOTHING, db_column='cid', primary_key=True)
class Meta:
managed = False
db_table = 'b'
unique_together = (('cid', 'aid'),)
class C(models.Model):
id = models.UUIDField(primary_key=True)
class Meta:
managed = False
db_table = 'c'
note the OneToOneField is now on cid. I suspect this is a bug, but I am inexperienced so wanted to ask here before reporting.
Secondary question: if this is a bug, is it worth reporting? Maybe the database design is very poor or uncommon?
I suspect this is a bug, but I am inexperienced so wanted to ask here before reporting.
Not really. Django does not work with primary keys that span over two or more columns, or at least not at the moment of writing. It has been requested, for example by ticket #373, but the designers decided to set this on "wontfix".
The documentation furthermore explains this:
Do Django models support multiple-column primary keys?
No. Only single-column primary keys are supported.
But this isn’t an issue in practice, because there’s nothing stopping
you from adding other constraints (using the unique_together model
option or creating the constraint directly in your database), and
enforcing the uniqueness at that level. Single-column primary keys are
needed for things such as the admin interface to work; e.g., you need
a single value to specify an object to edit or delete.
Likely you will not be able to do that in the (near) future, since a lot of Django tooling is built with the assumption that primary keys are "scalar" entities.
You thus likely should slightly redesign your existing database, and try to run inspectdb after that.
I try to create a web app using django and connecting to a SQL Server Database. The table that I use to display the data in a django form consists of 2 columns. Both of them being a foreign key and both of them together building the primary key of the table
CREATE TABLE [dbo].[MyTable]( [ID_Field1] [int] NOT NULL,
[ID_Field2] [int] NOT NULL, CONSTRAINT [PK_Movies2Genres] PRIMARY
KEY CLUSTERED ( [ID_Field1] ASC, [ID_Field2] ASC )WITH (PAD_INDEX =
OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON
[PRIMARY]
ALTER TABLE [dbo].[MyTable] WITH CHECK ADD CONSTRAINT [FK_Field2]
FOREIGN KEY([ID_Field2]) REFERENCES [dbo].[Table2] ([ID_Field2])
ALTER TABLE [dbo].[MyTable] CHECK CONSTRAINT [FK_Field2]
ALTER TABLE [dbo].[MyTable] WITH CHECK ADD CONSTRAINT [FK_Field1]
FOREIGN KEY([ID_Field1]) REFERENCES [dbo].[Table1] ([ID_Movie])
ALTER TABLE [dbo].[MyTable] CHECK CONSTRAINT [FK_Field1]
Now, django apperantly cannot create a model corresponding to this kind of sql table structure, i.e. it cannot create a primary key consisting of more than one field. Instead, it sets the primary key on one of the 2 columns and in the meta section of the model class it sets
unique_together = (('id_field1', 'id_field2'),)
The complete model:
class MyTable(models.Model):
id_field1 = models.ForeignKey(Table1, on_delete=models.DO_NOTHING, db_column='ID_Field1')
id_field2 = models.ForeignKey(Table2, on_delete=models.DO_NOTHING, db_column='ID_Field2')
class Meta:
managed = False
db_table = 'MyTable'
unique_together = (('id_field1', 'id_field2'),)
However, this is what django inspectdb tells me to do. I know that django creates automatically a field called id when there is no primary key defined. This seems to be the case here, although there is a primary key defined. Any idea how to deal with that problem?
use: https://github.com/onysos/django-composite-foreignkey
class Customer(models.Model):
company = models.IntegerField()
customer_id = models.IntegerField()
name = models.CharField(max_length=255)
address = CompositeForeignKey(Address, on_delete=CASCADE, to_fields={
"tiers_id": "customer_id",
"company": LocalFieldValue("company"),
"type_tiers": RawFieldValue("C")
})
class Meta(object):
unique_together = [
("company", "customer_id"),
]
class Contact(models.Model):
company_code = models.IntegerField()
customer_code = models.IntegerField()
surname = models.CharField(max_length=255)
# virtual field
customer = CompositeForeignKey(Customer, on_delete=CASCADE, related_name='contacts', to_fields={
"customer_id": "customer_code",
"company": "company_code"
})