Django, Ajax + Generic foreign keys - python

I have a generic foreign key in one of my models:
# models.py
class Tasks(models.Model):
content_type = models.ForeignKey(ContentType, limit_choices_to=tasktype_limits, null=True, blank=True)
object_id = models.PositiveIntegerField(null=True, blank=True, )
target = generic.GenericForeignKey('content_type', 'object_id')
ttype = models.ForeignKey('TaskType')
status = models.CharField(max_length = 60, null=False, blank=False)
comments = models.TextField(null=True, blank=True, )
Now I'd like to fetch all the tasks and it's "targets" with AJAX:
# views.py
def get_tasks(request, task_id):
tasks = Tasks.objects.all()
return HttpResponse(serializers.serialize('json', tasks))`
The Ajax-Call is working so far, but it doesn't return the objects related to the target-field.
How can I do that?

I had serious problems using JSON and Generic Keys, this is the method i used to solve my problem. I first made a list of the thing i need for example:
some_list = [some.pk,some.CONTENT_OBJECT.name] for some in GenericModel.objects.all()]
Then, dump the data with simple json found in django.utils
data = simplejson.dumps(some_list)
and then return the data to the template
return HttpResponse(data, mimetype='aplication/json')
Hope it helps.

Not sure if this is related, but there was a bug reported about the serialization of contenttypes (#7052; see related discussion). I believe it was fixed in Django 1.2. Which version of Django are you using?

Related

Django - Annotate post query set data with if current user has "liked" the post

So I have this model
model.py
class Post(models.Model):
uuid = models.UUIDField(primary_key=True, default=generate_ulid_as_uuid, editable=False)
created = models.DateTimeField('Created at', auto_now_add=True)
updated_at = models.DateTimeField('Last updated at', auto_now=True, blank=True, null=True)
creator = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="post_creator")
body = models.CharField(max_length=POST_MAX_LEN, validators=[MinLengthValidator(POST_MIN_LEN)])
class LikePost(AbstractSimpleModel):
creator = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="like_post")
post = models.ForeignKey(Post)
class User(AbstractDatesModel):
uuid = models.UUIDField(primary_key=True)
username = models.CharField(max_length=USERNAME_MAX_LEN, unique=True, validators=[
MinLengthValidator(USERNAME_MIN_LEN)])
created = models.DateTimeField('Created at', auto_now_add=True)
updated_at = models.DateTimeField('Last updated at', auto_now=True, blank=True, null=True)
Then I also have this annotator for returning a bunch of data outside of the Post table
annotator.py
def query_to_full_post_data_serializer(post_query_set: QuerySet):
query_set_annotated = post_query_set.annotate(
creator_username=F('creator__username'),
user_liked=F(<flag for each post in query for if user liked the post>)
reply_count=Count('postreply', distinct=True),
like_count=Count('likepost', distinct=True),
).prefetch_related(
Prefetch('photo', Photo.objects.order_by('-created')),
Prefetch('video', Video.objects.order_by('-created'))
)
return FullPostDataSerializer(query_set_annotated, many=True)
I'd like to return a field called "user_liked", which returns a boolean for each post in a query set that is True if the current logged in user has liked it or is the creator of a LikePost to a Post. When the request comes in I get the current user making the request so I can get their uuid. I'd like to use that uuid to check if the user has liked a post in the query set. How do I check if the current logged in user has liked a Post object in a query set Django?
I'm assuming you can do something like user_liked=F('likepost', filter=creator__uuid=current_user_uuid), but that wouldn't be a boolean that'd return user. If I really wanted I could do user_liked=Count('likepost, filter=creator__uuid=current_user_uuid) seems kind of inefficient though.
Sorry for my previous wrong answer.
So I'm quite rusty in the Django ORM so that query might not have best optimisation. Here is what I have come up with
Post.objects.all().annotate(liked_by_user=Q(likepost__creator=user))
The issue being that it will add duplicates for each reverse relationship it finds. That's why you should not use that and use a many to many.
For example, with a many to many, it would be as simple as
class User2(models.Model):
_id = models.BigAutoField(primary_key=True)
class Post2(models.Model):
_id = models.BigAutoField(primary_key=True)
creators = models.ManyToManyField(User2)
and now you only need
Post2.object.all().annotate(liked_by_user=Q(creator__in=[user]))
which is way better.

Find entities for specific user through multiple tables in Django Admin

I searched through stackoverflow about this particular scenario, but could not find a concrete answer, so i'm posting this.
So my problem is that i need to display specific records to a specific user in Django Admin. I'm aware that i can get the concrete logged in user through the get_queryset method extracting it from the request object. But the issue is i need to look through 6 tables to get to the information about the user of the recommendations so i could know which recommendation to display to him.
For example, if the records i need to display come from a Recommendation table, it has a reference to TableA, which has a reference to TableB .... which has a reference to TableF which has a reference to the User.
I'm aware i could do this by executing a plain SQL query with multiple joins, but my guess is that there must be a pythonic or Django sophisticated solution to this. But i may be wrong.
The model is unfortunately not in my control, nor i can change it, so i'm left to work with the state of the model that there is.
Thanks in advance.
EDIT: Unfortunately, i can't share details of it, but i can share the general look of it. So i think this should be enough to have a picture of my problem.
from django.db import models
from django.contrib.auth.models import User
class TableF(models.Model):
information = models.CharField(max_length=256, null=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class TableE(models.Model):
information = models.CharField(max_length=256, null=False)
tableF = models.ForeignKey(TableF, on_delete=models.CASCADE)
class TableC(models.Model):
information = models.CharField(max_length=256, null=False)
tableEs = models.ManyToManyField(TableE, through='TableD')
class TableD(models.Model):
information = models.CharField(max_length=256, null=False)
tableC = models.ForeignKey(TableC, on_delete=models.CASCADE)
tableE = models.ForeignKey(TableE, on_delete=models.CASCADE)
class TableA(models.Model):
information = models.CharField(max_length=256, null=False)
tableCs = models.ManyToManyField(TableC, through='TableB')
class TableB(models.Model):
information = models.CharField(max_length=256, null=False)
tableA = models.ForeignKey(TableA, on_delete=models.CASCADE)
tableC = models.ForeignKey(TableC, on_delete=models.CASCADE)
class Recommendation(models.Model):
information = models.CharField(max_length=256, null=False)
tableA = models.ForeignKey(TableA, on_delete=models.CASCADE)
you can use a middleware to include de user to the thread locals and catch this user from get_queryset in the model manager.
from threading import local
_thread_locals = local()
def get_current_user():
return getattr(_thread_locals, 'user', None)
class ThreadLocals(object):
#staticmethod
def process_request(request):
_thread_locals.user = getattr(request, 'user', None)
in the settings
MIDDLEWARE = [
...
'path.to.file.ThreadLocals',
]
from your.path import get_current_user
class TableFManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(creator=get_current_user())
class TableF(models.Model):
information = models.CharField(max_length=256, null=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
objects = TableFManager()
another less invasive option could be to rewrite the get_queryset in the admin class. there you already have the user in the request
def get_rec_user(user):
tes = TableE.objects.filter(tableF__in=TableF.objects.filter(user=user))
aes = TableB.objects.filter(tableE__in=tes).values_list('tableA_id', flat=True)
return Recommendation.objects.filter(
tableA__in=TableA.objects.filter(id__in=aes)
)

Fixing Error in Django Rest Framework Model Serializer Related Primary Key Queryset

I feel like I'm chasing my tail here and so I've come to your fine folks to help me understand where I've screwed up and why my thinking on this must be flawed in some way.
I'm writing an API in DRF and while it doesn't have a lot of tables in the DB, there are many to many relationships in the DB which makes it feel complicated, or at least it makes it difficult to just view the db tables and intuitively understand what's related.
First my model. I'm getting this when attempting to POST a new object to the jobs model. I'm needing to validate that the requesting worker has the permission to create a job with the related target and workergroup
Models:
class Workers(models.Model):
class Meta:
ordering = ['id']
workerid = models.CharField(max_length=16, verbose_name="Worker ID", unique=True, default="Empty")
customer = models.ForeignKey(Customers, on_delete=models.DO_NOTHING, default=1)
workername = models.CharField(max_length=64, verbose_name="Worker Friendly Name", default="")
datecreated = models.DateTimeField(auto_now_add=True)
awsarn = models.CharField(max_length=60, verbose_name="ARN Name of Worker", blank=True, null=True)
customerrights = models.ManyToManyField(Customers, related_name="access_rights", default="")
class Targets(models.Model):
class Meta:
ordering = ['id']
customer = models.ForeignKey(Customers, on_delete=models.SET_NULL, default=1, null=True)
friendly_name = models.CharField(max_length=70, verbose_name="Target Friendly Name", unique=False)
hostname = models.CharField(max_length=120, verbose_name="Target Hostname", default="")
ipaddr = models.GenericIPAddressField(protocol='both', unpack_ipv4=True, default="", null=True)
class WorkerGroups(models.Model):
class Meta:
ordering = ['id']
name = models.CharField(max_length=60, default="Default Group")
workers = models.ManyToManyField(Workers)
class Jobs(models.Model):
target = models.ForeignKey(Targets, on_delete=models.DO_NOTHING)
datecreated = models.DateTimeField(auto_now_add=True)
startdate = models.DateTimeField()
enddate = models.DateTimeField(null=True)
frequency = models.TimeField(default='00:05')
workergroup = models.ForeignKey(WorkerGroups, on_delete=models.DO_NOTHING)
jobdefinition = models.ForeignKey(JobDefinitions, on_delete=models.DO_NOTHING)
In my serializers I have a JobSerializer which is referencing PrimaryKeyRelatedField classes which I think should have the effect of limiting the queryset which validates those related models. BTW, the customerrightsids are being built in the view and that seems to work fine for all other models.
Serializers:
def get_queryset(self):
print("In Custom TargetPK get_queryset")
queryset = Targets.objects.filter(customer_id__in=self.context['auth'].customerrightsids)
if isinstance(queryset, (QuerySet, Manager)):
queryset = queryset.all()
return queryset
class WorkerGroupPKSerializer(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
print("In Custom WorkerGroupPK get_queryset")
queryset = WorkerGroups.objects.filter(workers__customer_id__in=self.context['auth'].customerrightsids)
if isinstance(queryset, (QuerySet, Manager)):
queryset = queryset.all()
return queryset
class JobSerializer(serializers.ModelSerializer):
workergroup = WorkerGroupPKSerializer(many=False) # Commenting this out removes error
target = TargetPKSerializer(many=False) # This seems to work fine even though it's similar to the line above
class Meta:
model = Jobs
fields = '__all__'
def create(self, validated_data):
print(self.context['auth'])
return super().create(validated_data)
There's nothing special in the create method of the viewset object. It's taking the request and passing a couple into the ViewSet and updating its context. I can share that if needed, but that doesn't seem to be where the issue is.
So finally, for the error. When I perform a POST to /jobs/ I'm getting the following:
Error:
MultipleObjectsReturned at /jobs/
get() returned more than one WorkerGroups -- it returned 2!
The error clearly states that I'm getting multiple WorkerGroups returned in a get() but I don't know where or how to resolve that in this case.
It is clearly a problem with the WorkerGroupPKSerializer. If I comment out the reference to it from the JobSerializer the error goes away. That stops the validation of that field though, so that's not a workable solution!
I'm not 100% sure I'm on the right track here, but it seems you might have a problem of duplicate/multiple results. You should try and use .distinct() on the Queryset in WorkerGroupPKSerializer (also, see Django docs). You used a ForeignKey on the customer property of the Worker model, so that makes it possible to have multiple Worker's belonging to the same WorkerGroup matching the filter query, thus returning the same WorkerGroup twice. So when the id of that WorkerGroup is POSTed, the get() will match two results, throwing that error.
Commenting out seems to clear the error, and this would be because of the fact that you're also commenting out many=False, thus, .get() is not called anymore. But as mentioned in the question, this would disable the custom queryset used to filter on.
Thanks to Sasja's answer. Here is the updated WorkerGroupPKSerializer that now works.
class WorkerGroupPKSerializer(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
print("In Custom WorkerGroupPK get_queryset")
queryset = WorkerGroups.objects.filter(workers__customer_id__in=self.context['auth'].customerrightsids).distinct()
if isinstance(queryset, (QuerySet, Manager)):
queryset = queryset.all()
return queryset

Django efficient Queryset with Foreign Key Models

I'm trying to find the most efficient way (as less db queries as possible) for the following model structure.
In my template I then want to pass all the data from all 3 models because I would have to show the post data as well as looping through the comments to create a comments list and display all the attachments for the different comments.
class Post(BaseModel):
user = models.ForeignKey('User', blank=True, null=True,
title = models.CharField(max_length=128)
content = models.TextField()
class Comment(BaseModel):
post = models.ForeignKey('Post', on_delete=models.CASCADE)
user = models.ForeignKey('User', on_delete=models.SET_NULL)
text = models.TextField()
class CommentAttachment(BaseModel):
comment = models.ForeignKey('Comment', on_delete=models.CASCADE)
name = models.CharField(max_length=128)
Should I fetch all data from CommentAttachment direction (meaning fetching all CommentAttachments where comment__post__id is the post id and then get all other data with select_related) or is there another way to start from the Post Model?
You can use prefetch_related or select_related in your query:
posts = Post.objects.filter(user=some_user).prefetch_related(
'comment_set', 'comment_set__commentattachment_set'
)
For example, after making a query as mentioned, the following command may retrieve all the comments for the first post in the queryset without making a SQL query:
posts.first().comment_set.all()

NOT NULL constraint failed when linking a model with User

I have a model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
first_name = models.CharField(max_length=30, default='anon')
last_name = models.CharField(max_length=30, default='anon')
interest = models.CharField(max_length=30, default='nothing')
def __str__(self):
return 'Username:%s' % self.user.username
class Post(models.Model):
title = models.TextField(default='No title')
text = models.TextField(max_length=220)
vote = models.IntegerField(default=0)
user_post = models.ForeignKey(User, related_name='post')
class Comments(models.Model):
name = models.TextField(default='Anon', null=True)
comment = models.TextField(max_length=2000, null=True)
post = models.ForeignKey(Post)
def __str__(self):
return 'comment:%s' % self.comment
In the post you can see I'm linking a post with the User. I read that you can access a users comments this way by using user.post_set.all(), so I gave it a try. When I attempt to migrate the change(adding the foriegn key in Post), I get an error.
django.db.utils.IntegrityError: NOT NULL constraint failed:
boardapp_post__new.user_post_id
I notice the post__new, so here is my view named new, which creates a post.
def new(request):
if request.method == 'POST':
post = Post.objects.create(title=request.POST.get('post_title', ''),
text=request.POST.get('post_text',''),vote=0
)
return HttpResponseRedirect(reverse('view_post', args=(post.id,)))
return render(request, 'new.html')
I'm new to creating users in django and am confused as to where not null consraint is failing. Thanks
When you migrate to your new database format, there should be a default User for the user_posts that already exist.
Django will try to fill this value for you in existing user_posts, but it doesn't know which value to chose, since null is not allowed in your current model.
Thus: you need to tell Django either 1) not to worry about Posts without a user (null = True) or 2) supply a default User, which is a bit harder (probably would require a function call that creates some dummy User on the fly).
So the easiest solution is to alter your Post model and change user_post:
user_post = models.ForeignKey(User, related_name='post', null=True)
It is failing because your model field is
user_post = models.ForeignKey(User, related_name='post')
it means every post will be assigned to a user(not null field).
From your view, you are not assigning any user to the post, so change your code to
post = Post.objects.create(title=request.POST.get('post_title', ''),
text=request.POST.get('post_text',''), vote=0, user_post=request.user
)

Categories