I have this code:
#api model
class VideoResource(ModelResource):
class Meta:
queryset = Video.objects.all()
include_resource_uri = False
resource_name = 'video'
authorization = DjangoAuthorization()
class QuestionResource(ModelResource):
user = fields.ToOneField(UserResource,'user',full=True)
video = fields.ForeignKey(VideoResource,'video',full=True)
class Meta:
queryset = Question.objects.all()
resource_name = 'question'
include_resource_uri = False
authorization = DjangoAuthorization()
def obj_create(self, bundle, request=None, **kwargs):
import json
temp = json.loads(request.body, object_hook=_decode_dict)
video = Video.objects.get(pk=temp['video'])
return super(QuestionResource, self).obj_create(bundle, request, user=request.user, video=video)
#model
class Question(models.Model):
text = models.CharField('Question',max_length=120)
created = models.DateTimeField(auto_now_add=True)
enabled = models.BooleanField(default=True)
flag = models.BooleanField(default=False)
allow_comments = models.BooleanField(default=True)
thumbnail_url = models.CharField(default='video.jpg',blank=True, null=True,max_length=200)
user = models.ForeignKey(User)
video = models.ForeignKey(Video)
def __unicode__(self):
return self.text;
class Video(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now_add=True)
url = models.URLField(default="")
user = models.ForeignKey(User)
def __unicode__(self):
return str(self.pk) + ' > ' + self.status
The problem is that I am getting this error when sending this object:
{"video":21,"text":"sadasds"}
The 'video' field has was given data that was not a URI, not a
dictionary-alike and does not have a 'pk' attribute: 21.
If I comment this line:
video = fields.ForeignKey(VideoResource,'video',full=True)
Everything works fine, but then I cannot get this information (video)
when asking to /api/v1/questions/
My question is:
Should I create to resources, one to post and another to retrieve
information <- this seems not a really good solution.
or
How can I create Nested Resources ? I tried to follow the example on
the web http://django-tastypie.readthedocs.org/en/latest/cookbook.html#nested-resources
but as you can see for some reason is not working.
maybe your eyes can help me find the error :)
Thanks!
The 'video' field has was given data that was not a URI, not a dictionary-alike and does not have a 'pk' attribute: 21.
So, this means that the integer 21 does't meet the requirements for that field, it also give a vague hint of what will meet the requirements.
first, you can send in the URI for the record, this is probably the most correct way as URIs are really unique while pk's are not.
{"video":"/api/v1/video/21","text":"sadasds"}
or, you can send in an dictionary-alike object with the pk field set.
{"video":{'pk':21},"text":"sadasds"}
The reason it works when you comment out the ForeignKey field is because then tastypie treats it as a IntegerField, which can be referenced by a plain integer.
This had me stunted for a while to, hope it helps!
Related
I feel like I am missing something simple. Working on a small personal project I have a MongoDB which I am connecting to via Djongo. I have started making a front end and have a problem with a collection using the generic MongoDB Object ID. The view will return a 404, while using the same query within MongoDB returns the desired document. Other pages are fine, this is the only one that uses the ObjectIDField as it was a hasty late additon
Model:
class Railway_Firmware(models.Model):
_id = models.ObjectIdField(name='_id', verbose_name='_id', db_column='_id', editable=False, max_length=200, primary_key=True)
qa = models.CharField(name='QA', verbose_name='QA', db_column='QA', max_length=200)
security = models.CharField(name='Security', verbose_name='Security', db_column='Security', max_length=200)
objects = models.Manager()
class Meta:
db_table = "Firmware"
verbose_name_plural = "firmware"
def get_absolute_url(self):
return reverse('firmware-detail-view', args=[self._id])
def __str__(self):
return str(self._id)
View:
class Railway_Firmware_DetailView(generic.DetailView):
model = Railway_Firmware
context_object_name = 'firmware'
template_name = 'firmware/firmware_detail.html'
pk_url_kwarg = '_id'
def get_queryset(self):
print({'_id': ObjectId(self.kwargs['_id'])})
return Railway_Firmware.objects.filter(**{'_id': ObjectId(self.kwargs['_id'])})
URL:
urlpatterns = [ path('firmware/<slug:_id>/', views.Railway_Firmware_DetailView.as_view(), name='firmware-detail-view'),
]
Admin works as expected with the same 631b1ce580404ce3f61d4565 within the link for example:
/admin/web_dev/railway_firmware/631929cc0b15c01285ae87e1/change/.
Printing the query to console: {'_id': ObjectId('631b1ce580404ce3f61d4565')} this looks correct so I am unsure where this is going wrong. Any help would be greatly appreciated, thanks!
The name of the URLConf keyword argument that contains the slug is _id in your route.
You have specified that pk_url_kwarg = '_id' in your DetailView.
Therefore, the wrong filter is applied to your queryset seeing as you don't have a pk field in your document.
queryset = queryset.filter(pk=pk)
You need to inform DetailView to use the slug field when building the filter for the queryset via its get_object method.
class Railway_Firmware_DetailView(generic.DetailView):
model = Railway_Firmware
context_object_name = 'firmware'
template_name = 'firmware/firmware_detail.html'
slug_url_kwarg = '_id'
slug_field = '_id'
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
# Ensure that _id value is an ObjectId type for query filters to work
self.kwargs["_id"] = ObjectId(self.kwargs["_id"])
I have a database table called Supplier that has a foreign key of User, each User has their own Suppliers. I got the get request working so that it returns all Suppliers in the entire table, but I can not find a way to filter it so I only receive the Suppliers associated with the User requested.
I am accessing this request by this URL:
http://localhost:8000/pm/getsuppliers/primary-key-of-user/
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
bio = models.CharField(max_length=200)
email = models.CharField(max_length=200)
class Supplier(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
phone = models.IntegerField()
email = models.EmailField(max_length=200)
views.py:
class getsuppliers(viewsets.ModelViewSet):
queryset = Supplier.objects.all()
serializer_class = GetSuppliersSerializer
lookup_field = 'user'
serializers.py:
class GetSuppliersSerializer(serializers.ModelSerializer):
class Meta:
model=Supplier
fields=['pk','user','name','email','phone']
The error I am receiving:
ERROR: pm.models.Supplier.MultipleObjectsReturned: get() returned more than one Supplier -- it returned 10!
I have done some searching on this error and they are saying to use .filter instead of .all in the view, but I am not sure how to make it return ALL Suppliers for the requested User, this seems like it would only return 1. Maybe I am wrong, hopefully someone has an easy solution!
You'll have to set the serializer's model (inside Meta) to User, and add in a supplier_set field:
class GetUserSuppliersSerializer(serializers.ModelSerializer):
supplier_set = SupplierSerializer(read_only=True, many=True)
class Meta:
model = User
fields = ['supplier_set']
class SuppliersSerializer(serializers.ModelSerializer):
class Meta:
model = Supplier
fields = ['pk','user','name','email','phone']
And also change the viewset queryset to get users.
Edit:
To answer the question in the comment, there are a few different ways to do that based on what you need, one way is to add a to_representation method in your GetUserSuppliersSerializer as such:
def to_representation(self, instance):
response = super().to_representation(instance)
response["supplier_set"] = sorted(response["supplier_set"], key=lambda x: x["pk"])
return response
I was remaking a social media site as a revision of Django and the rest framework, I didn't want to use the django default linear id count and didn't like how long the uuid library's ids was, so I used the shortuuid library. I've used them on the posts and the comments just to keep the anonymity of the count of both posts and comments. On the posts side everything works for the CRUD stuff (which should be proof that the issue isn't from the shortuuid library, as far as I know), although with the comments the Create Retrieve works perfectly but the Update Destroy doesn't. so here is the code we are working with:
starting with the models to know what kind of data we are working with (models.py):
from shortuuid.django_fields import ShortUUIDField
... # posts likes etc
class Comment(models.Model):
id = ShortUUIDField(primary_key=True, length=8, max_length=10)
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
body = models.TextField(max_length=350)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
ordering = ['created']
def __str__(self):
return f'on {self.post} by {self.user}'
objects = models.Manager()
serializers.py:
class CommentSerializer(ModelSerializer):
username = SerializerMethodField()
def get_username(self, comment):
return str(comment.user)
class Meta:
model = Comment
fields = ['id', 'user', 'post', 'username', 'body', 'created', 'updated']
read_only_fields = ['id', 'post', 'user', 'username']
now with the routing (urls.py):
from django.urls import path
from .views import *
urlpatterns = [
...
path('<str:pk>/comments/' , Comments),
path('<str:pk>/comments/create/', CreateComment),
path('<str:pk>/comments/<str:cm>/', ModifyComment),
# pk = post ID
# cm = comment ID
]
views.py:
class ModifyComment(generics.RetrieveUpdateDestroyAPIView):
serializer_class = CommentSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self):
post = Post.objects.get(pk=self.kwargs['pk'])
comment = Comment.objects.get(post=post, pk=self.kwargs['cm'])
return comment
def perform_update(self, serializer):
print(Post.objects.all())
post = Post.objects.get(pk=self.kwargs['pk'])
comment = Comment.objects.filter(pk=self.kwargs['cm'], post=post)
if self.request.user != comment.user:
raise ValidationError('you can\'t edit another user\'s post')
if comment.exists():
serializer.save(user=self.request.user, comment=comment)
else:
raise ValidationError('the comment doesnt exist lol')
def delete(self, request, *args, **kwargs):
comment = Comment.objects.filter(user=self.request.user, pk=self.kwargs['cm'])
if comment.exists():
return self.destroy(request, *args, **kwargs)
else:
raise ValidationError("you can\'t delete another user\'s post")
ModifyComment = ModifyComment.as_view()
and the response to going to the url '<str:pk>/comments/<str:cm>/' comment of some post we get this:
side note, the perform_update function doesn't seem to be called ever, even putting a print statement at the beginning of the function doesn't get printed so the issue may have to do with the get_queryset even though I've tried using the normal queryset=Comment.object.all() and making the get_queryset function return the comment with the correct params but I couldn't make it work
For individual objects you need to overwrite the get_object method.
You are performing the request GET /str:pk/comments/str:cm/, this calls the retrieve method on the view, which in turn calls get_object. The default behaviour is trying to find a Comment object with id equal to pk since it's the first argument, since you need to filter through a different model you need to overwrite it.
classy drf is a good website for seing how the internals of the clases work.
I have a User, Post and Tag model in Django. Tag model is not relevant for this topic. I can get all the data to the front end with nested objects. In the other hand when i want to create a new post i send the post data to django and in django view i am trying to update the data with relating the logged user to the "Post" but when i do that it gives me;
{'owner': {'username': [ErrorDetail(string='A user with that username already exists.', code='unique')]}}
error. How can i solve this error ?
models.py;
class Post(models.Model):
# Post specs
title = models.CharField(max_length=100, null=False)
place = models.CharField(max_length=100, null=False)
notes = models.CharField(max_length=10000, null=False)
tags = models.ManyToManyField(Tag)
start_date = models.DateField(null=True)
end_date = models.DateField(null=True)
created_at = models.DateField(auto_now=True)
owner = models.ForeignKey(User , null = True, on_delete=models.SET_NULL)
serializers.py;
class PostSerializer(serializers.ModelSerializer):
tags = serializers.SlugRelatedField(
many=True,
queryset=Tag.objects.all(),
slug_field='name'
)
owner = UserSerializer()
class Meta:
model = Post
fields = ('title','place','notes','start_date','end_date','created_at','id','owner','tags')
By the way if i change serializer.py like
owner = UserSerializer
it gives just primary key value. In front end i cant to anything with a integer number and i dont want to make an another api call for user model. Lastly view post function;
def post(self, request, format =None):
"""
Creates a post
"""
post = request.data ## copy dictionary to a variable
authenticatedUserDataAsDict = request.user.__class__.objects.filter(pk=request.user.id).values().first()
post.update( {'owner': authenticatedUserDataAsDict} ) ## attach authenticated user to post end
serializer = PostSerializer(data = post) ## serialize the dict
if serializer.is_valid():
serializer.save() ## if data valid save it.
return Response(serializer.data, status = status.HTTP_201_CREATED)
print("not valid->",serializer.errors)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST) # if it's not raise http 400
SOLVED
Hi again, it seems that rest framework have no idea about our request(create or get wise) because we are dealing with nested serializers.
So i found this article in medium and it helped me to solve my problem.
I'm trying to save the details of responses from apis (typically undocumented).
The api's are likely to change over time and I will re request the data from them periodically which often is likely to have no change.
I therefore just want to save responses when they change and a time stamp when I last checked the response with the api.
I did start to model via the Django ORM, but I've most likely make it overly complicated trying to normalise the data.
Requests can be as usual, plain GET request, GET with query or POST with data.
Responses should really be unique to a url (scheme, hostname and path), and query or payload, but I'm not sure how to best model this. And I feel the response JSON or text has a many 2 one relationship with the response. However I feel there must be a simpler way as I really just started with not wanting to duplicate the storage of responses more than necessary as mentioned above.
class Request(models.Model):
scheme = models.CharField(max_length=10)
hostname = models.TextField()
path = models.TextField(null=True)
method = models.CharField(max_length=10)
headers = models.TextField()
class Meta:
unique_together = ('scheme', 'hostname', 'path', 'method')
def __str__(self):
return self.url
class RequestQuery(models.Model):
request = models.ForeignKey(Request)
query = models.TextField()
def __str__(self):
return self.query
class RequestPayload(models.Model):
request = models.ForeignKey(Request)
payload = models.TextField()
def __str__(self):
return self.payload
class Response(models.Model):
request = models.ForeignKey(Request)
query = models.ForeignKey(RequestQuery, null=True)
payload = models.ForeignKey(RequestPayload, null=True)
headers = models.TextField()
status_code = models.IntegerField()
json = JSONField(null=True) # stores in either json or text depending on if valid json
text = models.TextField(null=True)
class Meta:
unique_together = ('request', 'status_code')
def _str_(self):
return str(self.request.url) + str(self.status_code)
class ResponseHistory(models.Model):
time_stamp = models.DateTimeField(auto_now_add=True)
response = models.ForeignKey(Response)
def __str__(self):
return self.time_stamp