Django: create many-to-Many relationship without creating intermediary table - django

I have a set of tables that Ive turned into django models. The tables weren't architected very well and i can't change them, and it is causing problems for this one many to many relationship I'm trying to set up in my code.
Models
class Owner(models.Model):
owner_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, unique=True) # Field name made lowercase.
email = models.TextField(max_length=200, unique=True)
is_active = models.BooleanField( blank=True, null=True) # Field name made lowercase.
owned_apps_whistic = models.ManyToManyField("Applications", through="ApplicationOwnerXRef", related_name="owners_whistic")
#owned_apps_group = models.ManyToManyField("Applications", through="BusinessUnitOwnerXref", related_name="owners_group")
class Applications(models.Model):
application_id = models.UUIDField(primary_key=True)
url = models.TextField(blank=True, null=True)
name = models.TextField(blank=True, null=True)
service = models.TextField(blank=True, null=True)
status = models.TextField(blank=True, null=True)
created_date = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
business_unit_name = models.TextField(blank=True, null=True)
class BusinessUnitOwnerXref(models.Model):
id = models.AutoField(primary_key=True)
owner_uuid = models.ForeignKey(ApplicationOwner, models.DO_NOTHING, to_field="owner_uuid", db_column = "owner_uuid")
business_unit_name = models.TextField(max_length=100, null=True)
Owners can own applications by owning the group that owns the application, indicated in the BusinessUnitOwnerXref table.
The Application table and the BusinessUnitOwnerXref both have the business_unit_name column , and can be directly joined
I cant use a foreign key because the BusinessUnitOwnerXref.business_unit_name column isn't unique.
TLDR, Is there a way to create the many-to-many relationship between Applications and Owners, through BusinessUnitOwnerXref, without altering the existing tables or letting django create another table?

Related

Django query fails with _id that is not used or created or referenced to

I have used the queryset before though this is my first attempt to JOIN tables but it's not working so far.
I am using django 3.2 and python 3.8.1
my models.py
class Mainjoinbook(models.Model):
fullsitename = models.TextField(primary_key=True)
creationdate = models.DateTimeField()
entrytypeid = models.BigIntegerField(blank=True, null=True)
title = models.TextField(blank=True, null=True)
tickettype = models.TextField(blank=True, null=True)
ticket = models.TextField(blank=True, null=True)
status = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'mainlogbook'
class Sitelocation(models.Model):
site_name = models.TextField(primary_key=True)
latitude = models.TextField(blank=True, null=True)
longitude = models.TextField(blank=True, null=True)
sites = models.ForeignKey(Mainjoinbook, on_delete=models.DO_NOTHING)
class Meta:
managed = False
db_table = 'tblsiteaccess'
I am trying to get all values from both tables joined in my views.py
qrylocations = Sitelocation.objects.select_related('sites').filter(sites__status='OPEN')
this results in this error as that column is created by django but doesn't belong to the table. I still can't workout how to resolve this as I have tried many options but always get in some kind of error and I hope someone can help me to see what I'm doing wrong in joining the tables on the primary keys defined
psycopg2.errors.UndefinedColumn: column tblsiteaccess.sites_id does not exist
the SQL output shown is as below.
output from qrylocations.query
SELECT "tblsiteaccess"."site_name", "tblsiteaccess"."latitude", "tblsiteaccess"."longitude", "tblsiteaccess"."sites_id", "mainlogbook"."fullsitename", "mainlogbook"."log_id", "mainlogbook"."creationdate", "mainlogbook"."entrytypeid", "mainlogbook"."title", "mainlogbook"."tickettype", "mainlogbook"."ticket", "mainlogbook"."status" FROM "tblsiteaccess" INNER JOIN "mainlogbook" ON ("tblsiteaccess"."sites_id" = "mainlogbook"."fullsitename") WHERE "mainlogbook"."status" = OPEN
A ForeignKey naturally needs a column in the database table. Since site_name itself is the primary key you should use that as a ForeignKey here, infact instead of a ForeignKey this needs to be a OneToOneField [Django docs] since it is also a primary key and needs to be unique:
class Sitelocation(models.Model):
site_name = models.OneToOneField(
Mainjoinbook,
on_delete=models.CASCADE,
primary_key=True,
db_column='site_name'
)
latitude = models.TextField(blank=True, null=True)
longitude = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tblsiteaccess'
I had a same problem. db_column option is helpful.
In this case...
class Sitelocation(models.Model):
site_name = models.TextField(primary_key=True)
latitude = models.TextField(blank=True, null=True)
longitude = models.TextField(blank=True, null=True)
sites = models.ForeignKey(Mainjoinbook,
on_delete=models.DO_NOTHING,
db_column="sites"
)
class Meta:
managed = False
db_table = 'tblsiteaccess'

Django views: postgres materialized view accessible via get but not via filter (both queryset)

I try to filter materialized postgres views in Django views.py. The database and the views were created with postgres. I can filter views which represent one to many relationships and I can access views with get (queryset) which represent many to many relationships. But I cannot filter those views which represent many to many relationships. Models were created with inspectdb. It's a postgis legacy database.
How do I have to filter these views?
models.py
fid = models.AutoField(primary_key=True)
id_dokument = models.IntegerField(blank=True, null=True)
dokument = models.CharField(max_length=50, blank=True, null=True)
datei = models.CharField(max_length=100, blank=True, null=True)
beschreibung = models.CharField(max_length=1024, blank=True, null=True)
datum = models.DateField(blank=True, null=True)
person = models.CharField(max_length=50, blank=True, null=True)
dokumenttyp = models.CharField(max_length=30, blank=True, null=True)
id_objekt = models.IntegerField(blank=True, null=True)
objekt = models.CharField(max_length=50, blank=True, null=True)
class Meta:
managed = False # Created from a view. Don't remove.
db_table = 'objekt_dokumente_rel'
views.py
dokumente = ObjektDokumenteRel.objects.using('db').filter(id_objekt=fid)
If replacing filter with get I receive one object (as expected).
Adding __exact to id_objekt dokumente = ObjektDokumenteRel.objects.using('db').filter(id_objekt__exact=fid) resolves the problem. Has anyone an idea why?

Django - obtaining a child model from a parent by the childs one to one field?

Good Afternoon,
I have a pair of models like the below:
class DeviceCircuitSubnets(models.Model):
device = models.ForeignKey(Device, on_delete=models.CASCADE)
circuit = models.ForeignKey(Circuit, on_delete=models.CASCADE, blank=True, null=True)
subnet = models.ForeignKey(Subnet, on_delete=models.CASCADE)
...
class BGPData(models.Model):
device_circuit_subnet = models.OneToOneField(DeviceCircuitSubnets, verbose_name="Device", on_delete=models.CASCADE)
bgp_peer_as = models.CharField(max_length=20, verbose_name='BGP Peer AS', blank=True, null=True)
bgp_session = models.CharField(max_length=10, verbose_name='BGP Session', blank=True, null=True)
bgp_routes = models.CharField(max_length=10, verbose_name='BGP Routes Received', blank=True, null=True)
service_status = models.CharField(max_length=10, verbose_name='Service Status', blank=True, null=True)
timestamp = models.DateTimeField(auto_now=True, blank=True, null=True)
I am filtering the DeviceCircuitSubnets and then I also want to access the BGPData related model via each filtered item.
service_data = DeviceCircuitSubnets.objects.filter(monitored=True, device__site_id=site_id) \
.select_related('device','circuit','subnet')
I have tried adding bgpdata to the select related and to prefetch but neither are currently working, I am returned with an error stating the model doesn't exist.
how would my query need to look to obtain each one to one field in a query set?
Thank you
Since you didn't set related_name attribute on OneToOneField, you need to use lower-cased model name for reverse relation:
service_data = DeviceCircuitSubnets.objects.filter(monitored=True, device__site_id=site_id) \
.select_related('bgpdata')
Note also that:
A DoesNotExist exception is raised when accessing the reverse relationship if an entry in the related table doesn’t exist

Filter django for objects containing subsets of same many to many relationship

In my database I have user objects with two many to many fields (messages and following) on them that both contain a many to many field related to another object Topic.
class User():
messages = ManyToManyField('Message', related_name='users', blank=True, null=True)
following = ForeignKey('Following', related_name='users', blank=True, null=True)
class Message():
date = DateField(blank=True, null=True)
content = TextField(blank=True, null=True)
topics = ManyToManyField('Topic', related_name='messages', blank=True, null=True)
class Following():
name = CharField(max_length=255, blank=True, null=True)
description = CharField(max_length=255, blank=True, null=True)
topics = ManyToManyField('Topic', related_name='following', blank=True, null=True)
class Topic():
name = CharField(max_length=255, blank=True, null=True)
source = CharField(max_length=255, blank=True, null=True)
I want to filter for all "users" who have "messages" attached to them that do not contain all of the topics attached to the "following" objects on the user.
Right now I am using a loop to accomplish this:
users = set()
for user in User.objects.filter(messages__isnull=False, following__isnull=False).iterator():
if not set(user.following.values_list('topics', flat=True))
).issubset(set(user.messages.values_list('topics', flat=True)):
users.add(user.pk)
Is there a way to accomplish the same thing with a single query?
---- EDIT ----
What I have is this:
User.objects.filter(following__isnull=False
).annotate(following_count=Count('following__topics', distinct=True)
).filter(following__topics__exact=F('message__topics')
).annotate(missing_topics=ExpressionWrapper(
F('following_count') - Count('message__topics', distinct=True),
IntegerField())
).filter(missing_topics__gt=0)
If there is a better way to do this or there are reasons why I should most definitely not do it this way, what are they?
---- EDIT ----
This question helped me to understand and use Håken Lid's answer
This is my new model and my new query:
class User():
messages = ManyToManyField('Message', related_name='users', blank=True, null=True)
following = ManyToManyField('Topic', through='Following', related_name='users', blank=True, null=True)
class Message():
date = DateField(blank=True, null=True)
content = TextField(blank=True, null=True)
topics = ManyToManyField('Topic', related_name='messages', blank=True, null=True)
class Following():
name = CharField(max_length=255, blank=True, null=True)
description = CharField(max_length=255, blank=True, null=True)
user = ForeignKey('User', related_name='following', blank=True, null=True)
topic = ForeignKey('Topic', related_name='following', blank=True, null=True)
class Topic():
name = CharField(max_length=255, blank=True, null=True)
source = CharField(max_length=255, blank=True, null=True)
User.objects.filter(~Q(messages__topics__in=F('following'))
).values('id').annotate(missing_topics=Count('following__topics', distinct=True))
This should be possible using a subquery.
First, make sure Following.topics uses a different related name than Messages.topics.
class Following(models.Model):
topics = ManyToManyField('Topic', related_name='following')
Then it should be possible to create a subquery. Something like this:
from django.db.models import OuterRef, Subquery
user_following_topic = Topic.objects.filter(following__users=OuterRef('pk'))
User.objects.exclude(messages__topics__in=Subquery(user_following_topics.values('pk')))
This might not work and give you your expected output exactly as written, but I think the principle should work for your case as well.
On the other hand, I don't really understand your database structure. It seems you use m2m relations where foreign keys could be more appropriate and simpler. The more complicated your relations are, the harder it is to create this kind of advanced query. And queries with lots of database joins can be very slow, since they might have to process huge amounts of data, compared to simple queries.
For example, instead of using m2m realitions, Following would make more sense to me like so:
class Following():
topic = ForeignKey('Topic', on_delete=models.CASCADE)
user = ForeignKey('User', on_delete=models.CASCADE)
client = models.CharField(max_length=255, blank=True, null=True)
duration = fields.DateRangeField(blank=False, null=False)
So basically a "through" model, as explained in the django docs on model relationships where there's a similar example.

Foreign key relationship error in django

I am developing a django app where I use a legacy database. This database doesn't have primary key or constraint in any table. This are two of my models
class Provider(models.Model):
id_provider = models.IntegerField(blank=True, null=True, primary_key=True)
name = models.CharField(max_length=50, blank=True)
type = models.CharField(max_length=10, blank=True)
signed = models.CharField(max_length=15, blank=True)
currency = models.CharField(max_length=1, blank=True)
class Meta:
managed = False
db_table = 'mpc_proveedores'
class Master(models.Model):
id_provider = models.ForeignKey(Proveedor)
anio = models.IntegerField(blank=True, null=True)
mes = models.IntegerField(blank=True, null=True)
nombre_prov = models.CharField(max_length=50, blank=True)
tipo_conci = models.CharField(max_length=30, blank=True)
f_recepcion = models.DateField(blank=True, null=True)
e_recepcion = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'mpc_master'
I need to show a table based on the model Master and I need to display the name of the provider instead of the id_provider.
This is my view
def monitorViewV2(request):
table = MonitorTable(Master.objects.all())
RequestConfig(request).configure(table)
return render(request,'monitorv2.html',{'table': table})
When I try to see the template in the browser, the next error is showed
ORA-00904: "MPC_MASTER"."ID_PROVIDER_ID": invalid identifier
I don't know if the error is because I don't have relationships in the database or if I need to create a function in the view to display the name instead of the id_provider.
Can you bring me some snippet or link where I can take some ideas to resolve this issue
Thanks in advance
Django needs there to be an integer DB column for foreign key relations, which will hold the PK of the related object. It looks like that column is id_provider in this case, so you should specify that in your field declaration. I'd also use a different field name to avoid confusion:
provider = models.ForeignKey(Proveedor, db_column='id_provider')
The docs provide a little more context:
https://docs.djangoproject.com/en/1.7/ref/models/fields/#database-representation