We have a postgres jsonb field in Django that allows users to store arbitrary user data. We wish to allow users to query this field but are unsure about the security implications.
The model
from django.db import models
class Item(models.Model):
user = models.ForeignKey("user", null=False)
meta = JSONField()
Query
def custom_query(operation, value):
qs = Item.objects.filter(user=user)
params = {
"meta__" + operation: value
}
qs = qs.filter(**params)
Usage:
Assuming meta is {"a": 1}.
custom_query(operation="contains", value={"a": 1})
custom_query(operation="a", value=1)
The above should be valid and equivalent queries.
Is this a secure way to perform the query?
I'd suggest adding an allowlist for valid operations and maybe checking the value is suitably simple (only strings, for example), but in the presence of other filters that ensure the rows that can be selected are those the user can see, I don't see a problem with this.
Related
from django.db import models
from djago.db.models import F, Q
class(models.Model):
order_date = models.DateField()
class OrderLine(models.Model):
order = models.ForeignKeyField(Order)
loading_date = models.DateField()
class Meta:
constraints = [
models.CheckConstraint(check=Q(loading_date__gte=F("order__order_date")), name="disallow_backdated_loading")
I want to make sure always orderline loading_date is higher than orderdate
A CHECK constraint can span over a column, or over a table, but not over multiple tables, so this is not possible through a CHECK constraint.
Some databases allow to define triggers. These triggers run for example when a records is created/updated, and can run SQL queries, and decide to reject the creation/update based on such queries, but currently, the Django ORM does not support that.
One could also work with a composite primary key of an id and the creation date of the order, in which case the create timestamp is thus stored in the OrderLine table, and thus one can implement a check at the table level, but Django does not support working with composite primary keys for a number of reasons.
Therefore, besides running raw SQL, for example with a migration file that has a RunSQL operation [Django-doc], but this will likely be specific towards a database.
Therefore probably the most sensical check is to override the model clean() method [Django-doc]. Django however does not run the .clean() method before saving an object in the database, this is only done by ModelForms, and ModelAdmins. We can thus add a check with:
from django.core.exceptions import ValidationError
class OrderLine(models.Model):
order = models.ForeignKeyField(
Order,
on_delete=models.CASCADE
)
loading_date = models.DateField()
def clean(self):
if self.loading_date < self.order.order_date:
raise ValidationError('Can not load before ordering')
return super().clean()
Which one would be optimal for creating the followers-following system in Django.
1.Foreign Key
class Follower(models.Model):
current_user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='following')
following_id = models.ForeignKey(User,on_delete=models.CASCADE,related_name='followers')
2.Many-To-Many
class Follower(models.Model):
current_user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='following')
followers = models.ManyToManyField(User)
So, for instance, if we want to retrive the following list of the user:
1.Foreign Key
Follower.objects.get(following_id=1).following.all()
2 Many-To-Many
User.object.get(user=1).follower_set.all().values('current_user')
If you want to add metadata about someone following you don't have much choice but to use a M2M with a through table. Otherwise, you might as well use option 1.
Consider the following Django model:
class Account(models.Model):
ACCOUNT_CHOICES = [
('m', 'Main',),
('s','Secondary'),
('o', 'Other')
]
user = models.ForeignKey(User)
level = models.CharField(max_length=1, choices=ACCOUNT_CHOICES)
How can I enforce a database constraint of a maximum of one 'Main' account per user, while still allowing users any number of 'Secondary' or 'Other' accounts? In a sense, I want unique_together for user and level, but only when the value of level is m.
I know that I can manually check on saving, but I would prefer the database to check automatically and raise an IntegrityError when appropriate.
I don't think you can do that with your current model, but if those are the only two choices for the level field, consider changing it to a nullable BooleanField, for example
is_main = models.BooleanField(null=True)
and set it to None for secondary accounts. Then a unique_together will work because every null value is unique as far as SQL is concerned (see this answer).
Since there are more choices for the level field as you later clarified, you may add a third field and possibly override the .save() method to have it automatically set to None if level is not "m" for extra convenience.
Edit: If you are not concerned about portability, #Trent has suggested that PostgreSQL supports partial unique indexes, for example:
create unique index u_i on accounts(user_id, level_id) WHERE level_id = 'm';
Here is an SQL Fiddle.
Edit 2: Actually it looks like it is finally possible to create partial indexes in Django ORM starting from Django 2.2. See this question for details.
I need something like this for selecting user and groups in a single query in django.
user = User.objects.select_related('groups').get(id=user_id)
I don't want to use raw queries for this.
There is another way to do this which makes two queries:
user = User.objects.get(id=user_id)
groups = user.groups
Edit: It seems there is no way to do that in one query except raw sql or extra.
So if you only want to get one user, your second example should work.
If many users:
You should use prefetch-related, but in two queries.
prefetch_related, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related. It also supports prefetching of GenericRelation and GenericForeignKey.
...
The additional queries in prefetch_related() are executed after the QuerySet has begun to be evaluated and the primary query has been executed.
So if you want to prefetch groups, it will execute two queries whether you use all or get.
You can use prefetch related in the view, and then pass it to the template
def view(request):
user = User.objects.all().select_related(
).prefetch_related(
'groups',
).get(pk=request.user.pk)
return render(request, 'app/template.html', {
'user': user,
})
You should select from Group model 'cause the relation is many to many.
I have not tried my answer but perhaps it will work;
group_list_with_user = Group.objects.filter(user = USERID).all()
thereafter you can access all groups which are assigned to user and access user for example;
group_list_with_user[0].name # group name
group_list_with_user[0].user.first_name # user's first name
I run a lab annotation website where users can annotate samples with tags relating to disease, tissue type, etc. Here is a simple example from models.py:
from django.contrib.auth.models import User
from django.db import models
class Sample(models.Model):
name = models.CharField(max_length = 255)
tags=models.ManyToManyField('Tag', through = 'Annot')
class Tag(models.Model):
name = models.CharField(max_length = 255)
class Annot(models.Model):
tag = models.ForeignKey('Tag')
sample = models.ForeignKey('Sample')
user = models.ForeignKey(User, null = True)
date = models.DateField(auto_now_add = True)
I'm looking for a query in django's ORM which will return the tags in which two users agree on the annotation of same tag. It would be helpful if I could supply a list of users to limit my query (if someone only believes User1 and User2 and wants to find the sample/tag pairs that only they agree on.)
I think I understood what you need. This one made me think, thanks! :-)
I believe the equivalent SQL query would be something like:
select t.name, s.name, count(user_id) count_of_users
from yourapp_annot a, yourapp_tag t, yourapp_sample s
where a.tag_id = t.id
and s.id = a.sample_id
group by t.name, s.name
having count_of_users > 1
While I try hard not to think in SQL when I'm coming up with django model navigation (it tends to get in the way); when it comes to aggregation queries it always helps me to visualize what the SQL would be.
In django we now have aggregations.
Here is what I came up with:
models.Annot.objects.select_related().values(
'tag__name','sample__name').annotate(
count_of_users=Count('user__id')).filter(count_of_users__gt=1)
The result set will contain the tag, the sample, and the count of users that tagged said sample with said tag.
Breaking it apart for the folks that are not used to django aggregation:
models.Annot.objects.select_related()
select_related() is forcing all tables related to Annot to be retrieved in the same query
This is what will allow me to specify tag__name and sample__name in the values() call
values('tag__name','sample__name')
values() is limiting the fields to retrieve to tag.name and sample.name
This makes sure that my aggregation on count of clients will group by just these fields
annotate(count_of_users=Count('user__id'))
annotate() adds an aggregation as an extra field to a query
filter(count_of_users__gt=1)
And finally I filter on the aggregate count.
If you want to add an additional filter on what users should be taken into account, you need to do this:
models.Annot.objects.filter(user=[... list of users...]).select_related().values(
'tag__name','sample__name').annotate(
count_of_users=Count('user__id')).filter(count_of_users__gt=1)
I think that is it.
One thing... Notice that I used tag__name and sample__name in the query above. But your models do not specify that tag names and sample names are unique.
Should they be unique? Add a unique=True to the field definitions in the models.
Shouldn't they be unique? You need to replace tag__name and sample__name with tag__id and sample__id in the query above.