I have an external database which I can't modify in any way (read-only). It has three tables - Company (id), CompanyContact (company_id, contact_id), Contact (id, company_id).
Basically, Contact has a nullable foreign-key to Company table and it works as many-to-one, but if company_id is null, I have to look into CompanyContact table, which is many-to-many kind relationship.
How can I combine these two tables (Contact and CompanyContact) into one model - Contact? In other words, how can I get all contacts for a given company?
In SQL that would be something like:
select contact.id from contact where company_id = XXX
union
select contact_id from companycontact where company_id = XXX
Django models:
class Company(models.Model):
uuid = models.CharField(max_length=36, primary_key=True, db_column='id')
class Meta:
managed = False
class Contact(models.Model):
uuid = models.CharField(max_length=36, primary_key=True, db_column='id')
company = models.ForeignKey(Company, db_column='company_id')
class Meta:
managed = False
I don't have a model for CompanyContact. And there is nothing to show in views because that basically is my question, how to get contacts for a given company.
The original tables in your database are not properly structured. It's incorrect that the contact field should have a company_id field. That needlessly complicates all queries (raw SQL or Django) because an additional check is needed on the contact_id field.
On the other hand it's perfectly legit for two entities in a many to many relationship to have exactly one mapping instead of many. So there isn't one clear answer to this question. I suppose your best bet would be to add a ManyToMany field as well. I am making this suggestion purely on the fact that you say the data is read only
class Contact(models.Model):
uuid = models.CharField(max_length=36, primary_key=True, db_column='id')
company = models.ForeignKey(Company, db_column='company_id')
companies = models.ManyToManyField(Company, related_name='companies')
class Meta:
managed = False
Related
Here in my problem, I have a User model, in which a user (login) can be from the “Supplier” company or from “Customer” company.
It is a M2M relationship for both sets of tables: User-Customer and User-Supplier.
Can I link them this way:
company = models.ManyToManyField(Customer, Supplier, on_delete=models.PROTECT, related_name='Users')
enter image description here
Thanks!!
You can't do that.
A better approach will be if you use a Company model with a type of Supplier and Customer you can create an enum for this because you are using the same fields in both of your models so its good to have a type in a single model.
How to make enum in model with TextChoices
Company:
name
address
contact name
type
then in your User model
company = models.ManyToManyField(Company, on_delete=models.PROTECT, related_name='Users')
make more sense this way.
I have a database view that relates 2 companies by foreign keys like so:
DB company_view:
company1_id FK to Company,
company2_id FK to Company,
description text
where
--- some company criteria ---
I try model in Django as unmanaged like so:
class CompanyView(models.Model):
company1 = models.ForeignKey(Company, related_name='company1_id', parent_link=True)
company2 = models.ForeignKey(Company, related_name='company2_id', parent_link=True)
description = models.TextField()
class Meta:
managed = False
db_table = 'company_view'
For the Admin class I have:
#admin.register(models.CompanyView)
class CompanyViewAdmin(AdvancedModelAdmin):
list_display = ('company1', 'company2', 'description')
But the admin page throws exception like:
psycopg2.errors.UndefinedColumn: column company_view.id does not exist
It doesn't make sense to have a primary id key, so is there any way around this?
Thanks
why you dont make a many-to-many relationship?
company = models.ManyToManyField(Company)
look: https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_many/
this will solve your problem, since it use a object instance.
Imagine there are three models named Movie, Actor, and Participation.
class Movie(models.Model):
identifier = models.CharField()
class Actor(models.Model):
name = models.CharField()
class Participation(models.Model):
movie_identifier = models.CharField()
actor = models.ForgeinKey(Actor, on_delete=models.CASCADE)
Let's assume that I can't use ForgeinKey for the movie in the Participation model.
how can I retrieve all the participation records of a movie with only one query?
Here is the solution if I had a foreign key for the movie in the participation table:
qs = Movie.objects.filter(identifier="an_identiier").prefetch_related("participations_set")
How can I do this without having a Movie foreign key in the Participation model?
Thanks!
One of the most important things when designing a database (hence when designing your models) is database normalization [Wikipedia].
You talk about Participation being related to multiple models like Movie, Series, Episode, etc. this means that Movie, Series, Episode all can be said to have something in common or they can be said to be a specialization of another entity let us say Participatable for the lack of a better word, or we can say Participatable is a generalization of Movie, Series, Episode, etc.
How do we model these? Well we will just have an extra model that our other models will have a OneToOneField with:
class Participatable(models.Model):
# Any common fields here
MOVIE = 'M'
SERIES = 'S'
TYPE_CHOICES = [
(MOVIE, 'Movie'),
(SERIES, 'Series'),
]
subject = models.CharField(max_length=1, choices=TYPE_CHOICES)
class Movie(models.Model):
# uncommon fields
participatable = models.OneToOneField(
Participatable,
on_delete=models.CASCADE,
related_name='movie',
)
class Series(models.Model):
# uncommon fields
participatable = models.OneToOneField(
Participatable,
on_delete=models.CASCADE,
related_name='series',
)
class Participation(models.Model):
participatable = models.ForgeinKey(Participatable, on_delete=models.CASCADE)
actor = models.ForgeinKey(Actor, on_delete=models.CASCADE)
Other than this solution which I find is the best for such modelling you can go with using the content-types framework which will essentially do what you do currently. That is it will use a field that stores the related id and also a foreign key that points to an entry in a table that will simply describe which table this id is for.
My question is about creating a query which filter objects that are related through several intermediate tables. My relational database looks like this:
Any number of products can be uploaded by one user (one to many relationship). However, users also can rank products. A ranking can be completed by several users and a user can have several rankings (Many to many relationship). The same applies between Product and Ranking. I use explicit intermediate tables (Rank and Belong) which defines the M2M relationships by the through parameter, because they have additional information which describes the relationship.
The models code is something like this (I omitted irrelevant fields for simplicity):
class Product(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
belong= models.ManyToManyField(Ranking, through="Belong")
#...
#The M2M table which relates Product and Ranking
class Belong(models.Model):
ranking = models.ForeignKey(Ranking, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
#...
class Meta:
unique_together = (("ranking", "product"))
class Ranking(models.Model):
user= models.ManyToManyField(settings.AUTH_USER_MODEL, through="Rank")
created = models.DateTimeField(auto_now_add=True)
#...
#The M2M table which relates User and Ranking
class Rank(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
ranking = models.ForeignKey(Ranking, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
#...
class Meta:
unique_together = (("user", "ranking"))
#The AUTH_USER_MODEL, which is not included here
My question is: How can I create a query which filters products which has been ranked by a given user? This implies “following back” relations between Belong, Ranking and Rank tables. I tried this following the Django tutorial, but it didn´t work:
Product.objects.filter(belong__ranking__rank__user=”username”)
You're a bit confused between your M2M relationships, and their through models.
For example, I don't understand why your M2M from Product to Ranking is called "belong". It should be called "rankings". Your M2M from Ranking to User at least has the right basic name, but it points to many users so should be "users".
Nevertheless, the point is that when you follow the M2Ms, you don't need to take the through tables into account. And the other issue is that "user" is itself a model, so to compare with a username you would need to continue to follow to that field. So:
Product.objects.filter(belong__user__username="username")
I've got Django tables like the following (I've removed non-essential fields):
class Person(models.Model):
nameidx = models.IntegerField(primary_key=True)
name = models.CharField(max_length=300, verbose_name="Name")
class Owner(models.Model):
id = models.IntegerField(primary_key=True)
nameidx = models.IntegerField(null=True, blank=True) # is Person.nameidx
structidx = models.IntegerField() # is PlaceRef.structidx
class PlaceRef(models.Model):
id = models.IntegerField(primary_key=True)
structidx = models.IntegerField() # used for many things and not equivalent to placeidx
placeidx = models.IntegerField(null=True, blank=True) # is Place.placeidx
class Place(models.Model):
placeidx = models.IntegerField(primary_key=True)
county = models.CharField(max_length=36, null=True, blank=True)
name = models.CharField(max_length=300)
My question is as follows. If in my views.py file, I have a Person referenced by name and I want to find out all the Places they own as a QuerySet, what should I do?
I can get this far:
person = Person.objects.get(name=name)
owned_relations = Owner.objects.filter(nameidx=nameidx)
How do I get from here to Place? Should I use database methods?
I'm also not sure if I should be using ForeignKey for e.g. Owner.nameidx.
Thanks and apologies for this extremely basic question. I'm not sure how to learn the basics of database queries except by trying, failing, asking SO, trying again... :)
The whole point of foreign keys is for uses like yours. If you already know that Owner.nameidx refers to a Person, why not make it a foreign key (or a OneToOne field) to the Person table? Not only do you get the advantage of referential integrity - it makes it impossible to enter a value for nameidx that isn't a valid Person - the Django ORM will give you the ability to 'follow' the relationships easily:
owned_places = Place.objects.filter(placeref__owner__person=my_person)
will give you all the places owned by my_person.
Incidentally, you don't need to define the separate primary key fields - Django will do it for you, and make them autoincrement fields, which is almost always what you want.
If u could redesign.Then
In owner nameidx can be a foreign key to Person(nameidx)
Placeref(structidx) could be a foreign key to Owner(structidx) and
Place(placeidx) could be a foreign key Place ref(placeidx)
Then u could deduce the place value easily..