Django multiple foreign key to a same table - python

I need to log the transaction of the item movement in a warehouse. I've 3 tables as shown in the below image. However Django response error:
ERRORS:
chemstore.ItemTransaction: (models.E007) Field 'outbin' has column name 'bin_code_id' that is used by another field.
which is complaining of multiple uses of the same foreign key. Is my table design problem? or is it not allowed under Django? How can I achieve this under Django? thankyou
DB design
[Models]
class BinLocation(models.Model):
bin_code = models.CharField(max_length=10, unique=True)
desc = models.CharField(max_length=50)
def __str__(self):
return f"{self.bin_code}"
class Meta:
indexes = [models.Index(fields=['bin_code'])]
class ItemMaster(models.Model):
item_code = models.CharField(max_length=20, unique=True)
desc = models.CharField(max_length=50)
long_desc = models.CharField(max_length=150, blank=True)
helper_qty = models.DecimalField(max_digits=10, decimal_places=4)
unit = models.CharField(max_length=10, blank=False)
def __str__(self):
return f"{self.item_code}"
class Meta:
verbose_name = "Item"
verbose_name_plural = "Items"
indexes = [models.Index(fields=['item_code'])]
class ItemTransaction(models.Model):
trace_code = models.CharField(max_length=20, unique=False)
item_code = models.ForeignKey(
ItemMaster, related_name='trans', on_delete=models.CASCADE, null=False)
datetime = models.DateTimeField(auto_now=False, auto_now_add=False)
qty = models.DecimalField(max_digits=10, decimal_places=4)
unit = models.CharField(max_length=10, blank=False)
action = models.CharField(
max_length=1, choices=ACTION, blank=False, null=False)
in_bin = models.ForeignKey(
BinLocation, related_name='in_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
out_bin = models.ForeignKey(
BinLocation, related_name='out_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
remarks = models.TextField(blank=True)
def __str__(self):
return f"{self.trace_code} {self.datetime} {self.item_code} {dict(ACTION)[self.action]} {self.qty} {self.unit} {self.in_bin} {self.out_bin}"

you have same db_column in two fields so change it
in_bin = models.ForeignKey(
BinLocation, related_name='in_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
out_bin = models.ForeignKey(
BinLocation, related_name='out_logs', db_column='other_bin_code', on_delete=models.CASCADE, null=False) /*change db_column whatever you want but it should be unique*/

If are linked to the same model name, You should use different related_name for each foreign_key filed . here is the exemple :
address1 = models.ForeignKey(Address, verbose_name=_("Address1"),related_name="Address1", null=True, blank=True,on_delete=models.SET_NULL)
address2 = models.ForeignKey(Address, verbose_name=_("Address2"),related_name="Address2", null=True, blank=True,on_delete=models.SET_NULL)

thank you for everyone helped. According to Aleksei and Tabaane, it is my DB design issue (broken the RDBMS rule) rather than Django issue. I searched online and find something similar: ONE-TO-MANY DB design pattern
In my case, I should store in bin and out bin as separated transaction instead of both in and out in a single transaction. This is my solution. thankyou.
p.s. alternative solution: I keep in bin and out bin as single transaction, but I don't use foreign key for bins, query both in bin and out bin for the bin selection by client application.

Related

How to join 3 or more than 3 models in one single query ORM?

I am having 4 models linked with a foreign key,
class CustomUser(AbstractUser):
username = None
email = models.EmailField(('email address'), unique=True)
phone_no = models.CharField(max_length=255, unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = CustomUserManager()
def __str__(self):
return self.email
class personal_profile(models.Model):
custom_user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
picture = models.ImageField(default='profile_image/pro.png', upload_to='profile_image', blank=True)
role = models.CharField(max_length=255, blank=True, null=True)
gender = models.CharField(max_length=255, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
def __str__(self):
return str(self.pk)
class academia_profile(models.Model):
custom_user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
education_or_certificate = models.CharField(max_length=255, blank=True, null=True)
university = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
return str(self.pk)
class contact_profile(models.Model):
custom_user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
country = models.CharField(max_length=255, blank=True, null=True)
state = models.CharField(max_length=255, blank=True, null=True)
city = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
return str(self.pk)
For extracting the data of those four models, I need to extract it by querying 4 times differently and then by passsing for different variables to HTML templates it something a hectic plus would be reducing the performance speed (I am sure!)
My current queries be like
user_base = CustomUser.objects.get(id=user_id)
user_personal = personal_profile.objects.get(custom_user=user_id)
academia = academia_profile.objects.get(custom_user=user_id)
contact = contact_profile.objects.get(custom_user=user_id)
Is it possible to get all of the four queries values in a single variable by hitting a single join query in ORM ?
also, I want to extract just the country from contact_profile and picture from personal_profile in the join query.
Select_related() can able to work here but how? that's what I am not getting.
You are looking for prefetch_related:
Returns a QuerySet that will automatically retrieve, in a single batch, related objects for each of the specified lookups.
user_base = (
CustomUser
.objects
.prefetch_related( #<-- THIS!
"personal_profile_set",
"academia_profile_set",
"contact_profile_set")
.get(id=user_id))
personal_profile = user_base.personal_profile_set.all()[0]
academia_profile = user_base.academia_profile_set.all()[0]
contact_profile = user_base.contact_profile_set.all()[0]
Btw, if you have only one personal_profile, academia_profile, contact_profile per CustomUser, consider changing ForeignKey by OneToOneField and use select_related.

Python django query database and get all FKs in list

I am new in django and I am working in order to implement a Rest API. My issue is with a query that I can't find a working solution no matter the number of hours spent on it. More specifically I have these two models:
class Subcategory(models.Model):
id = models.UUIDField(primary_key=True, editable=False, null=False, blank=False, default=uuid.uuid4)
name = models.CharField(max_length=50, null=False, blank=False)
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=False, blank=False, db_column='category')
class Category(models.Model):
id = models.UUIDField(primary_key=True, editable=False, null=False, blank=False, default=uuid.uuid4)
name = models.CharField(max_length=50, null=False)
image = models.CharField(max_length=100, default=None)
As you can see each category has one or more subcategories and in the subcategory model I have a foreign key to the category. What i want to do is to query my database, get all categories and to each category add an extra field subcategories with a list of the subcategories. My idea it was to follow the FKs using .select_related but it seems to be a wrong solution since I am taking the following error:
"message": "name 'subcategory__category' is not defined"
My query is:
Category.objects.all().select_related(subcategory__category).values()
Any ideas on how to solve this issue and find a way to implement my query?
Thank you in advance
Do it like this
class Category(models.Model):
id = models.UUIDField(primary_key=True, editable=False, null=False, blank=False, default=uuid.uuid4)
name = models.CharField(max_length=50, null=False)
image = models.CharField(max_length=100, default=None)
sub_cat = ManyToManyField(Subcategory, blank=True)
class Subcategory(models.Model):
id = models.UUIDField(primary_key=True, editable=False, null=False, blank=False, default=uuid.uuid4)
name = models.CharField(max_length=50, null=False, blank=False)
def __str__(self):
return self.name
Now,query part
temp = []
all_cat = Category.objects.all()
for cat in all_cat:
temp.append( list(cat.sub_cat.all()) )
I think this is what you want to do.

How to get a models with highest value grouped by related model in django?

I would like to perform this query in django:
For each sensor, select it's latest message
In SQL it would be something like
SELECT * FROM (SELECT * FROM messages order by date_entered DESC) as order GROUP BY sensor_id
Models are defined like this
class BaseModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
date_entered = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
deleted = models.IntegerField(default=0)
class Meta:
abstract = True
ordering = ['date_modified']
class Device(BaseModel):
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
name = models.CharField(max_length=255, blank=False)
identifier = models.CharField(max_length=31, blank=False)
imei = models.CharField(max_length=31, blank=False)
status = models.CharField(max_length=31, blank=False)
settings = models.TextField()
class DeviceMessage(BaseModel):
device = models.ForeignKey(Device, on_delete=models.SET_NULL, blank=True, null=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
latitude = models.DecimalField(max_digits=9, decimal_places=6, blank=True, null=True)
longitude = models.DecimalField(max_digits=9, decimal_places=6, blank=True, null=True)
altitude = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True)
signal_strength = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
battery = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
satellites = models.IntegerField(blank=True, null=True)
size = models.IntegerField(blank=True, null=True)
raw = models.TextField(blank=True, null=True)
Is it possible to achieve this in django?
Basically, it is this problem Using ORDER BY and GROUP BY together
Since Django 1.11 there is the Subquery feature that you can use to annotate data:
from django.db.models import OuterRef, Subquery
latest = DeviceMessage.objects.filter(device_id=OuterRef('pk')).order_by('-date_entered')
devices = Device.objects.annotate(latest_message_id=Subquery(latest.values('pk')[:1]))
message_ids = [d.latest_message_id for d in devices]
for message in DeviceMessage.objects.filter(pk__in=message_ids).select_related('device'):
print(device.name, message)
......
"For each sensor, select it's latest message"
I assume by "sensor" you are referring to the Device model. In that case (since you've now clarified that BaseModel was an abstract base class), it is as simple as this (with sensor being the particular Device instance you're querying about):
sensor.devicemessage_set.order_by("-date_entered")[0]
If you want to find this information for multiple sensors at once, you might be better off using an annotation, for example:
from django.db.models import Max
...
Device.objects.all().annotate(latest_msg=Max("devicemssage_set__date_entered"))
This gives each Device object a (temporary) property called latest_msg which holds the information you want.

Django doesn't see fk in other table

I have some models and fk on them to others.
models.py
class ElementMessages(models.Model)
element = models.ForeignKey(Element, on_delete=models.CASCADE)
sender = models.ForeignKey(UserAccount, on_delete=models.SET_NULL, null=True)
text = models.TextField(max_length=512, null=True)
send_time = models.DateTimeField(auto_now_add=True)
type = models.CharField(max_length=16, choices=MESSAGE_TYPES, default=SIMPLE)
type_dialog = models.CharField(max_length=10, choices=DIALOG_TYPE, default=DIALOG_TALK)
request = models.ForeignKey(ChatRequest, null=True, default=None, on_delete=models.CASCADE)
post_work = models.ForeignKey(PostWork, null=True, default=None, on_delete=models.CASCADE)
files = models.BooleanField(default=False)
class Element(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
artist = models.ForeignKey(Artist, on_delete=models.CASCADE, related_name='chat_element', null=True, blank=True)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='chat_element', null=True, blank=True)
element = models.ForeignKey('projects.Element', null=False, on_delete=models.CASCADE, related_name='chat_element')
When I try to delete Element object, it raises this:
django.db.utils.IntegrityError: insert or update on table "chat_elementmessages" violates foreign key constraint "chat_elementmessages_element_id_672e2ba2_fk_chat_element_id"
DETAIL: Key (element_id)=(87cdd8d7-47f0-4264-8aa7-ae21a8246fd8) is not present in table "chat_element".
But when I look at table in db, this key exists.
How to fix that?
As it turned out, problems were at Django pre_delete andpost_delete signals. They tried to refer to a non-existing object, that I'm try to delete. Fixed with simple check on the existence of the object.

Python django Need return name instead of id foreginkey

class Product(models.Model):
name = models.CharField(max_length=300, default='default name')
description = models.TextField(max_length=800, blank=True, null=True)
link = models.TextField(default='0')
type = models.ForeignKey(ProductType, on_delete=models.CASCADE, null=True, blank=True)
category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, null=True, blank=True)
keyword = models.OneToOneField(KeyWords, on_delete=models.CASCADE, null=True, blank=True)
model don't return data from ForeignKey
p = Product.objects.get(...)
p.type.name
p.category.name
I think you need to define a __str__ method in your ProductCategory model. This way you will have a descriptive name for each entry into that table. Without it, your entry is best described by the id field.

Categories

Resources