In Django I have a model that links a tenant to a facility (house). The tenant and the facility needs to be in the same site (city) for this link to be valid. To enforce this I want to add a constraint that checks if the tenant's site and the facility's site matches.
I have tried to add a CheckConstraint to the model, but this compiles to a migration that is always false.
Model:
class TenantInFacility(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.DO_NOTHING)
facility = models.ForeignKey(Facility, on_delete=models.DO_NOTHING)
percentage_used = models.DecimalField(default=100, max_digits=6, decimal_places=3)
start = models.DateTimeField(default=datetime.datetime.fromtimestamp(0))
end = models.DateTimeField(default=None, blank=True, null=True)
class Meta:
verbose_name_plural = "tenants in facilities"
constraints = [
models.CheckConstraint(check=models.Q(models.F("tenant__site") == models.F("facility__site")),
name='tenant_facility_sites_match')
]
def __str__(self):
return self.tenant.site.name + ": " + self.tenant.name + " in " + self.facility.name
Migration:
operations = [
migrations.AddConstraint(
model_name='tenantinfacility',
constraint=models.CheckConstraint(check=models.Q(False), name='tenant_facility_sites_match'),
),
]
Is there a way in Django to require a foreign key's foreign key to match another foreign key's foreign key.
In diagram form:
+----------+
| Tenant |
+----------+
/ \
/ \
+--------------------+ +------+
| Tenant in Facility | | Site |
+--------------------+ +------+
\ /
\ /
+----------+
| Facility |
+----------+
Related
I'd like to output a table using "Premises" model in Django admin. In addition I'd like include the output in this table of an additional column, say "last utility payment". It is actually a column in a related table. There may be no payment in the database so admin should able to see either an empty cell or the date of payment.
I was able to write a DB query that displays the information I need. Its significant and worked part is given below:
SELECT jp.id,
jp.number apartment,
jp.building_number building,
jp.rent,
jp.arrears,
jpm.last_payment
FROM jasmin_premises jp
LEFT JOIN (
SELECT pm.premises_id,
max(pm.paid) last_payment
FROM jasmin_payment pm
GROUP BY pm.premises_id
) jpm ON jp.id = jpm.premises_id;
And the output is similar to the following:
id | apartment | building | rent | arrears | last_payment
--------------------------------------------------------------
170 | 1X | 6-A | 297.43 | 2.57, | NULL
72 | 2 | 4 | 289.66 | -678.38 | 2021-01-31
173 | 3Z | 7 | 432.86 | 515.72 | 2021-02-04
73 | 4 | 8-B | 292.25 | 515.44 | 2021-02-04
74 | 5 | 8-B | 112.42 | 3249.34 | NULL
75 | 6A | 122 | 328.48 | 386.23 | 2021-02-04
76 | 7 | 42 | 482.06 | 964.12 | 2021-01-31
77 | 8 | 1 | 433.71 | 867.42 | 2021-01-31
78 | 9C | 12 | 322.79 | 322.79 | 2021-02-04
79 | 10 | 122 | 324.22 | 0 | 2021-02-04
80 | 12 | 12 | 322.79 | 1232.46 | NULL
81 | 14 | 5-Z | 440.82 | 978.44 | 2021-02-04
And I'm using the following models (only a significant part):
class Premises(models.Model):
number = models.CharField(
blank=False,
null=False,
max_length=10)
building_number = models.CharField(
blank=False,
null=False,
max_length=3)
rent = models.DecimalField(
blank=False,
null=False,
max_digits=12,
decimal_places=2,
default=0.0)
area = models.DecimalField(
blank=False,
null=False,
max_digits=5,
decimal_places=2)
class Payment(models.Model):
paid = models.DateField(
blank=False,
null=False)
premises = models.ForeignKey(
Premises,
blank=False,
null=False,
on_delete=models.CASCADE,
related_name='payments',
db_index=True)
Is there a way to override admin.ModelAdmin.get_queryset (for example using annotations) to get an extra column like in my example above? Is there any other way to make a LEFT JOIN on a compound DB query using Django ORM?
to make this query in django you have to add the models.Manager() to the tables like this:
models.py
class Premises(models.Model):
# existent code
objects = models.Manager()
class Payment(models.Model):
# existent code
objects = models.Manager()
In the part of app you want to access this information
from .models import Premises, Payment
premises = Premises.objects.all()
data_to_display = []
for premise in premises:
payments = Payment.objects.filter(premises=premise).order_by('-paid')
if len(payments) == 0:
last_payment = "Null"
else:
last_payment = payments[0]
object_to_list = {
"id": premise.id,
"apartment": premise.number,
"building": premise.building_number,
"rent": premise.rent,
"arreaars": premise.area,
"last_payment": last_payment.paid
}
data_to_display.append(object_to_list)
The solution is to add an explicit subquery to a QuerySet using the Subquery expression. We'll need also to use OuterRef because a queryset in a Subquery needs to refer to a field from the outer query.
So let's create a subquery:
from django.db.models import OuterRef
payments = Payment.objects.filter(
premises=OuterRef('pk')
).order_by('-paid')
The next step is to pass payments subquery to a queryset:
from django.db.models import Subquery
# 'payments' here is from example above
premises = Premises.objects.annotate(
last_payment=Subquery(payments.values('paid')[:1])
)
Finally, lets see the used SQL to query objects rows the database:
print(premises.query)
(the output is formatted, only the significant part is shown)
SELECT "jasmin_premises"."id",
"jasmin_premises"."number",
"jasmin_premises"."building_number",
"jasmin_premises"."arrears",
"jasmin_premises"."rent",
(SELECT U0."paid"
FROM "jasmin_payment" U0
WHERE U0."premises_id" = "jasmin_premises"."id"
ORDER BY U0."paid" DESC
LIMIT 1) AS "last_payment"
FROM "jasmin_premises";
Now, after performing the tests, we can use this in our ModelAdmin:
from django.contrib import admin
from django.db.models import OuterRef, Subquery
from .models import Payment, Premises
#admin.register(Premises)
class PremisesAdmin(admin.ModelAdmin):
list_display = (
'number',
'building_number',
'rent',
'arrears',
'last_payment',
)
def get_queryset(self, request):
qs = super().get_queryset(request)
payments = Payment.objects.filter(
premises=OuterRef('pk')
).order_by('-paid')
qs = qs.annotate(
last_payment=Subquery(payments.values('paid')[:1]),
)
return qs
def last_payment(self, obj):
return obj.last_payment
last_payment.short_description = 'Last payment'
last_payment.admin_order_field = 'last_payment'
Well, this doesn't use JOINs, but this approach will force Django to execute a subquery.
Probably in some cases it may be possible to write an equivalent queryset that performs the same task more clearly or efficiently however, this is the best I have achieved so far.
I am working on a Django project where I need to link one table(model) to each user.
Assume MyTable_1 maps to user_1 and so on.
The primary key for MyTable will be a DateField which contains continuous dates from the time user signed-up.
MyTable_1 for User_1
|-----------|----------|-------------|-----------------|
| Date(PK) | food_ate | game_played | ran_today |
|-----------|----------|-------------|-----------------|
| 10/01/20 | rice | chess | Yes |
|-----------|----------|-------------|-----------------|
| 11/01/20 |sandwhich | tennis | No |
|-----------|----------|-------------|-----------------|
MyTable_2 for User_2
|-----------|----------|-------------|-----------------|
| Date(PK) | food_ate | game_played | ran_today |
|-----------|----------|-------------|-----------------|
| 16/03/19 | pizza | rugby | Yes |
|-----------|----------|-------------|-----------------|
| 17/03/19 | pasta | football | Yes |
|-----------|----------|-------------|-----------------|
And so on for every new user created. User logs in those information in MyTable.
How can I implement this? I am using PostgreSQL and have written custom User Model.
You really don't need seperate tables just seperate rows.
A ForeignKey relation will do the trick, something like this in your models.py:
# user model:
User(models.Model, ...):
first_name = models.CharField(...)
last_name = models.CharField(...)
...
# log model:
Log(models.Model):
user = models.ForeignKey(User, ...)
date = models.DateField(...)
food_ate = models.CharField(...)
game_played = models.CharField(...)
ran_today = models.CharField(...)
class Meta: unique_together = ('user', 'date',)
Then, elsewhere, you can access your users' logs like so:
user = User.objects.get(id='the_user_id')
logs = user.logs.all()
I am using Django model translation for the multi-linguistic site(English(default), Arabic, French), the problem is when saving data in django admin default overrides both Arabic and French translation fields. But in database actual value presents.
My Model:
plan/models.py
class operators(models.Model):
country = models.ForeignKey(Country, on_delete=models.CASCADE)
operator = models.CharField(max_length=100, null=True, blank=True)
image = models.ImageField(verbose_name=_('Operator Logo'), max_length=255,null=True,blank=True)
class Meta:
verbose_name = 'Operator'
verbose_name_plural = 'Operators'
db_table = 'operator'
def __str__(self):
return self.operator
plan/translation.py:
#register(operators)
class OperatorsTranslationOptions(TranslationOptions):
fields = ('operator',)
plan/admin.py:
class OperatorAdmin(admin.ModelAdmin):
list_display = ('operator', 'country')
.
.
.
admin.site.register(operators, OperatorAdmin)
settings.py:
INSTALLED_APPS = [
'modeltranslation',
.
.
.
'plan',
]
.
.
.
LANGUAGES = (
('en', gettext('English')),
('ar', gettext('Arabic')),
('fr', gettext('French')),
)
.
.
MODELTRANSLATION_TRANSLATION_FILES = (
'plan.translation',
)
MODELTRANSLATION_DEBUG = True
Below is the database value
planbaker=# select * from operator;
id | operator | image | country_id | operator_ar | operator_en | operator_fr
----+------------+-----------------------+------------+--------------+-------------+-------------
1 | airtel-ind | pb_Zu2y9BE.jpeg | 1 | | |
3 | aircel | 360_360_1_vsXSDEo.JPG | 2 | ايرتل | aircel |
2 | aircel-sa | 352_hQ4TZVq.jpeg | 2 | زين السعودية | aircel-sa | aircel-sa
But in Admin all the field have operator value only in english .
Can somebody please tell me where i went wrong? I tried both updte_translation_fields and sync_translation_fields but not helping
Try using modeltranslation.admin.TranslationAdmin instead of ModelAdmin for your OperatorAdmin class.
In order to be able to edit the translations via the django.contrib.admin application you need to register a special admin class for the translated models. The admin class must derive from modeltranslation.admin.TranslationAdmin which does some funky patching on all your models registered for translation.
https://django-modeltranslation.readthedocs.io/en/latest/admin.html
Just started Django.
I have 2 models. Radusergroup and expiration. username is primary key on Radusergroup and a OnetoOne Field in expiration with primary_key=True. Django is trying query for username_id in expiration model although the field itself is username only.
When I dont explicitly define Managed=False it also tries to change the username field in expiration table from the database to username_id as well.
What am I doing wrong here ?
class Radusergroup(models.Model):
username = models.CharField(max_length=64,primary_key=True)
groupname = models.CharField(max_length=64)
priority = models.IntegerField()
class Meta:
managed = False
class expiration(models.Model):
username = models.OneToOneField(Radusergroup,on_delete=models.CASCADE, to_field='username', primary_key=True)
expiration = models.DateTimeField()
class Meta:
managed = False
python .\manage.py shell
>>> help(expiration())
Help on expiration in module panel_app.models object:
class expiration(django.db.models.base.Model)
| expiration(*args, **kwargs)
|
| expiration(username, expiration)
|
| Method resolution order:
| expiration
| django.db.models.base.Model
| builtins.object
|
| Methods defined here:
|
| expiration = <django.db.models.query_utils.DeferredAttribute object>
| get_next_by_expiration = _method(self, *, field=<django.db.models.fields.DateTimeField: expiration>, is_next=True, **
kwargs)
|
| get_previous_by_expiration = _method(self, *, field=<django.db.models.fields.DateTimeField: expiration>, is_next=Fals
e, **kwargs)
|
| username_id = <django.db.models.query_utils.DeferredAttribute object>
| ----------------------------------------------------------------------
Thanks dirkgroten for pointing me to the right direction.
Adding db_column solved my problem
class expiration(models.Model):
username = models.OneToOneField(Radusergroup,on_delete=models.PROTECT, to_field='username',db_column="username" , primary_key=True)
I am developing a hirerchical application where leaf nodes can be instances of different models. I can't figure out how to make it work with django-mptt app. Is this even possible in that application? If yes, what am I doing wrong? and if no, is there anything out there what does what I am trying to do?
The following is a basic structure of the models:
class FolderItemBase(MPTTModel):
order = models.PositiveIntegerField()
class Meta:
abstract = True
class MPTTMeta:
parent_attr = 'folder'
order_insertion_by = ['order']
class Folder(FolderItemBase):
folder = TreeForeignKey('Folder', related_name='folders', blank=True, null=True)
...
class Image(FolderItemBase):
folder = TreeForeignKey('Gallery', related_name='images') # cannot be null since leaf has to be inside of a folder
...
When I try to do the following I am only able to get the Folder children, and none of the images. Same thing when I try to get ancestors of the images
>>> folder1 = Folder.objects.create(title='Folder 1', order=0)
>>> folder2 = Folder(title='Folder 2', order=0)
>>> folder2.insert_at(folder1, save=True)
>>> image = Image(...)
>>> image.insert_at(folder1, save=True)
>>> folder1.get_children()
[<Folder: Folder 2>]
>>> image.get_ancestores()
[]
And this is how things are stored in the db after all of this:
Folder table
----------------
+----+-------+-----+------+---------+-------+-----------+----------+
| ID | order | lft | rght | tree_id | level | folder_id | title |
+----+-------+-----+------+---------+-------+-----------+----------+
| 1 | 0 | 1 | 4 | 1 | 0 | | Folder 1 |
+----+-------+-----+------+---------+-------+-----------+----------+
| 2 | 0 | 2 | 3 | 1 | 1 | 1 | Folder 2 |
+----+-------+-----+------+---------+-------+-----------+----------+
Images Table
------------
+----+-------+-----+------+---------+-------+-----------+
| ID | order | lft | rght | tree_id | level | folder_id |
+----+-------+-----+------+---------+-------+-----------+
| 1 | 1 | 2 | 3 | 1 | 1 | 1 |
+----+-------+-----+------+---------+-------+-----------+
As you can see it figures what should be the level number for the image and the correct (at at least it seems to be) left and right numbers however it does not update anything in the folder table so then when you try to do a query, nothing gets selected.
Any pointers are appreciated. Thank you.
AFAIK, this is not possible; django-mptt piggy backs on Django's QuerySet, which will only ever work with one type of things. You can a use the contenttypes framework to associate the "real" item with something like FolderItem, which would only be used for the hierarchy, e.g.
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class FolderItem(MPTTModel):
folder = TreeForeignKey('Folder', related_name='folders', blank=True, null=True
order = models.PositiveIntegerField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Meta:
abstract = True
class MPTTMeta:
parent_attr = 'folder'
order_insertion_by = ['order']
Then, when you're using the django-mptt manager methods and such, you'll get back a queryset of FolderItems, and you can access the Folder/Image for each as you iterate over the set, through the generic foreign key.
However, be aware that this will likely be costly in terms of database queries, since each time you access a generic foreign key, a new query must be issued.