Django Model conditional relationship - django

Newbie question: Django + Postgres + PostGIS
I am trying to setup a Projects model in Django that needs to be conditionally related to a geometry model depending on its geometry type. the geometry types are : Points, Lines or Polygons. The question is how do I define this relationship in the Project Model so that I do not have to save the different geometry types in the same table ( Hence the 3 different geometry models)
=======================Here are my models ============================
PRJ_GEOM = (
(1, "point"),
(2, "line"),
(3, "polygon")
)
class Project(models.Model):
name = models.CharField(max_length=20)
project_geom_type = models.IntegerField(choices=PRJ_GEOM)
project_geometry = models.OneToOneField( ????) # I am stuck here - how do I express this conditional relationship which depends on project_geom_type
# Geometry Model
class Project_Point_Geom(models.Model):
project = models.OneToOne(Project, on_delete=models.CASCADE, related_name='project_point')
point = models.PointField()
class Project_Line_Geom(models.Model):
project = models.OneToOne(Project, on_delete=models.CASCADE, related_name='project_line')
line = models.LineStringField()
class Project_Polygon_Geom(models.Model):
project = models.OneToOne(Project, on_delete=models.CASCADE, related_name='project_polygon')
polygon = models.PolygonField()

Actually you only need one model. All geometry fields are derived from GeometryField so you can use it to store it's sub classes.
PRJ_GEOM = (
(1, "point"),
(2, "line"),
(3, "polygon")
)
class Project(models.Model):
name = models.CharField(max_length=20)
project_geom_type = models.IntegerField(choices=PRJ_GEOM)
project_geometry = models.GeometryField()
That is all! one model, one table instead of four which is much easier to maintain. And the data is slightly smaller because the relation field is eliminated.

Your Project model doesn't need the project_geometry fields. You only need to relate your different geometry models to the project using your OneToOne field. Django automatically creates reverse relationships for ForeignKey, ManyToMany and OneToOne relationships. Your classes would look something like this:
class Project(models.Model):
name = models.CharField(max_length=20)
class Point_Geo(models.Model):
project = models.OneToOne(Project) # truncated for example
point = models.PointField()
class Line_Geo(models.Model):
project = models.OneToOne(Project) # truncated for example
line = models.LineStringField()
class Polygon_Geo(models.Model):
project = models.OneToOne(Project) # truncated for example
polygon = models.PolygonField()
When you've got a Project instance you can walk back along the reverse relationship like this:
project = Project.objects.get(id=1)
point = project.point_geo.point
To query for all projects with point geometry you could query:
projects_with_points = Project.objects.exclude(point_geo=None)

Related

Multiple many-to-many relations through the same model in Django problem

i have a problem i don't know how to make multiple many-to-many relations through the same model.
This is my DB relations :
Db architecture
and this is my code, i want to know if what I did is right or not ?
class Projects(models.Model):
Project_name = models.CharField(max_length=50)
Poject_key = models.CharField(max_length=50)
CITools = models.ForeignKey(CITools,null=True,on_delete=models.CASCADE)
UsersO = models.ManyToManyField(User,through='OwnerShips') # for user Ownerships
UserF = models.ManyToManyField(User,through='Favorites') # for user Favorites
This is my OwnerSHips class :
class OwnerShips(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
project = models.ForeignKey(Projects,on_delete=models.CASCADE)
And this is my Favorites Class:
class Favorites(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
project = models.ForeignKey(Projects,on_delete=models.CASCADE)
I don't think you can use foreign keys (ownerships and favorites) in Project before those classes are created.
Actually i found the solution, i used the attribute related_name, so Django to create this two tables automatically

How are many-to-many relationship handled in Django?

I have a shirt which can contain multiple colors, and multiple colors which can have multiple shirts. Normally I would express it the following way:
In django I have the many-to-many (https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_many/)
Example:
publications = models.ManyToManyField(Publication)
--
Can I create the table "Item_colors" consisting of 2 columns (no "ID" primary key) and design the models according to my diagram using the composite key:
class Item_colors(models.Model):
class Meta:
unique_together = (('cloth_item_id', 'color_id'),)
cloth_item_id = models.ForeignKey(Cloth_item, on_delete=models.CASCADE)
color_id = models.ForeignKey(Color, on_delete=models.CASCADE)
How is the many-to-many relation handled in a DB context, and does it yield better performance?
EDIT: https://code.djangoproject.com/wiki/MultipleColumnPrimaryKeys no avoiding primary keys in favor of composite keys saving columns :( at least for now..
How is the many-to-many relation handled in a DB context, and does it yield better performance?
With a junction table in the middle, so with an item_colors table. But the table contains a primary key, as does every model in Django.
If you do not specify a through=… parameter [Django-doc] to define the model for the junction table yourself, Django will automatically create such model. This model then has two ForeignKeys to the two models it connects as discussed in the database representation section of the documentation:
Behind the scenes, Django creates an intermediary join table to represent the many-to-many relationship. 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. Since some databases don’t support table names above a certain length, these table names will be automatically truncated and a uniqueness hash will be used, e.g. author_books_9cdf. You can manually provide the name of the join table using the db_table option.
But the table thus has a primary key. This might be useful if the same object occurs a second time in the relation.
You can access the through model in the Article-Publication example for example with:
Article.publications.through
You thus can define a through model yourself, for example with:
class Color(models.Model):
color = models.CharField(max_length=128)
class ClothItem(models.Model):
item_name = models.CharField(max_length=128)
colors = models.ManyToManyField(
Color,
related_name='cloth_items'
through='ClothItemColors'
)
class ClothItemColors(models.Model):
cloth_item = models.ForeignKey(ClothItem, on_delete=models.CASCADE)
color = models.ForeignKey(Color, on_delete=models.CASCADE)
class Meta:
db_table = 'item_colors'
constraints = [
models.UniqueConstraint(
fields=('cloth_item', 'color'),
name='unique_cloth_color'
)
]
often an explicit through model is used to store extra information, for example the quantity:
class ClothItemColors(models.Model):
cloth_item = models.ForeignKey(ClothItem, on_delete=models.CASCADE)
color = models.ForeignKey(Color, on_delete=models.CASCADE)
quantity = models.IntegerField(default=0)
# …

How to create abstract model in Django?

I am new to Django framework and in my project I have a model called Layer.
class Layer(models.Model):
name = models.CharField(max_length=255)
I would like layer to have a relationship with an abstract model called "Geometry", more specifically, one layer should have one or no Geometry and one Geometry should be owned by one layer.
The problem is that I have for types of Geometries and they all have different properties, so I decided to create multiple geometries:
class Circle(models.Model):
radius = models.CharField(max_length=255)
class Rectangle(models.Model):
height = models.CharField(max_length=255)
width = models.CharField(max_length=255)
I would like to have a data structure where both models are of the same type (Geometry). I would like to call layer.geometry and be able to get either a circle or a rectangle, or a cross and so on. Is that possible? And how is the database shape going to be like? Is Django going to create two different tables or one table with merged properties?
Thanks in advance
Django supports inheritance, although it is not very common. You can thus create a model Geometry, and let Circle and Rectangle inherit from that:
class Geometry(models.Model):
pass
class Circle(Geometry):
radius = models.CharField(max_length=255)
class Rectangle(Geometry):
height = models.CharField(max_length=255)
width = models.CharField(max_length=255)
We can furthermore make a ForeignKey (or another relation) to this Geometry class with:
class Layer(models.Model):
name = models.CharField(max_length=255)
geometry = models.ForeignKey(Geometry, on_delete=models.CASCADE)
Django will make migrations that, for an SQL database create tables for Geometry, Circle and Rectangle. The Circle and Rectangle models will have an implicit OneToOneRelation to the table for the Geometry named geometry_ptr_id, that thus refers to the parent.
For more information, see the section on Multi-table inheritance in the documentation.
Although you can link Cirle and Rectangle models with ForeignKey or OneToOneField, your have an option to create an AbstractModel for Geometry model.
class Geometry(models.Model):
id = models.IntegerField()
class Meta:
abstract = True
class Circle(Geometry):
radius = models.CharField(max_length=255)
class Rectangle(Geometry):
height = models.CharField(max_length=255)
width = models.CharField(max_length=255)
As such, both Circle and Rectangle models will have id field from Geometry model, but will only have 2 tables in your database. However, you wont be able to query in Geometry model.
If you need to query explicitly for Geometry model, go for ForeignKey or OneToOneField option. If not, abstract model is a cleaner approach in my opinion.
From Django Docs: Often, you will just want to use the parent class to hold information that you don’t want to have to type out for each child model. This class isn’t going to ever be used in isolation, so Abstract base classes are what you’re after.

Django query using through model

a = models.ManyToManyField('self', through = 'x')
How to make a query on a by filtering through 'x'
You need to define symmetrical=False when creating the field. In Django 1.7 if you try your definition you will get an error similar to this:
CommandError: System check identified some issues:
ERRORS:
myapp.MyModel.a: (fields.E332) Many-to-many fields with intermediate tables must not be symmetrical.
so change the field to
a = models.ManyToManyField('self', through = 'x', symmetrical = False)
Now it all depends on your x class. It have to define two foreignKey fields back to yourModel:
class x(models.Model):
from_a = models.ForeignKey(myClass, related_name = 'from_a')
to_a = models.ForeignKey(myClass, related_name = 'to_a')
comment = models.CharField(max_length = 255)
Now you don't filter from x but from the reversed relations created by the FK's, i.e. something like this:
myClass.objects.filter(from_a__comment='something')
or from an instance perspective:
my_instance.a.filter(from_a__comments='something')
A great article about the topic can be found here: Self-referencing many-to-many through

Django reverse ForeignKey lookup returns None

I'm new to Django development and have just started writing an app.
I have two classes defined in models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class NEO(models.Model):
name = models.CharField(max_length=100, default=' ')
sighter = models.ForeignKey(User, blank=True, null=True)
date_sighted = models.DateTimeField(default=timezone.now())
ratings = models.IntegerField(default=0)
#coords = models.ForeignKey('Coords', default='')
def __unicode__(self):
return self.name
class Coords(models.Model):
ra = models.FloatField('Right Ascension', default=0)
dec = models.FloatField('Declination', default=0)
neo = models.ForeignKey(NEO, related_name='neo_coords', null=True)
def __unicode__(self):
return str(self.ra) + ' ' + str(self.dec)
Each Coords object links to a single NEO and vice versa.
Uncommenting the Neo.Coords line and then calling n.Coords returns a None. Given a NEO object, how can I get the corresponding Coords object?
ForeignKey here is a ManyToOne relationship (as suggested in the docs), So in your case multiple Coords objects can be binded to a single NEO object. If you want a OneToOne Relation you may want to use models.OneToOneField (documentation here).
In case of foreign key's lookup you can use.
NEO.coords_set.get(**lookup_arguments_here)
# Here NEO.coords_set is the list of coords objects bound to this particular NEO object.
and in case of OneToOne you can simply use
NEO.coords
It doesn't make sense to have two tables referencing each other with dual foreign keys because you run into a chicken or the egg problem. You need to decide whether or not there can be a one-to-many relation or a one-to-one relation.
Can a NEO have multiple Coords? Can a Coord have multiple NEOs? If the answer is yes, then you need a ForeignKey. The ForeignKey should be on the many of the one-to-many side of the relation. If the answer was no, and there can only be a one-to-one link, then you want a OneToOneField.
To access the reverse side of the relationship it is simple:
# multiple coords per neo
class NEO(models.Model):
name = ...
class Coords(models.Model):
name = ...
neo = models.ForeignKey(NEO)
c = Coords.objects.get(id=1)
c.neo # shows the neo
n = NEO.objects.get(id=1)
coords = n.coords_set.all() # multiple coords per neo
If instead you had a one to one relationship:
class NEO(models.Model):
name = ...
class Coords(models.Model):
name = ...
neo = models.OneToOneField(NEO)
c = Coords.objects.get(id=1)
c.neo # shows the neo
n = NEO.objects.get(id=1)
coords = n.coords # only one possible coord per neo
https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships