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.
Related
I am trying to make a button on the post that when a user cliks on it, is requesting to be added to the post as an attendance and then, the author of the post has to approve that request.
Models.py
class Attending(models.Model):
is_approved = models.BooleanField(default=False)
attending = models.ManyToManyField(User, related_name='user_event_attending')
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField(blank=True)
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
attending = models.ForeignKey(Attending, on_delete=models.CASCADE, verbose_name='atending', null=True)
My problem here is that every time I writte a query for the button is giving me erros and I couldn`t figure it out how to get the reverse of the foreign key.
This is my code on my views.py
def request_event(request, pk):
previous = request.META.get('HTTP_REFERER')
try:
query = Attending.objects.get(pk=pk)
request_attending = query.post_set.add(request.user)
messages.success(request, f'Request sent!')
return redirect(previous)
except query.DoesNotExist:
return redirect('/')
Thank you very much for your help in advance!
This: query.post_set is just relationship. You cannot call method add just like that. You can add to ManyToMany relation and I believe you want to add user to Attending.attending field, not directly to Post object. Change that to:
...
query = Attending.objects.get(pk=pk)
query.attending.add(request.user)
messages.success(request, f'Request sent!')
....
| Update |
I think you should consider rearraning your relationships. If I understand your plan, you should go this way:
class Attending(models.Model):
...
attendant = models.ForeignKey(User, related_name='events_attending', on_delete=models.CASCADE)
post = models.ForeignKey('Post', on_delete=models.CASCADE)
class Post(models.Model):
...
author = models.ForeignKey(User, on_delete=models.CASCADE)
For one Post object there can be many Attending objects, then you can use relations like that:
att = Attending.objects.first()
att.post # get related Post object from ForeignKey | there is only one
post = Post.objects.first()
post.attending_set.all() # get all related Attending objects
Post.objects.get(attending=att) # get Post object that the Attending object have in ForeignKey field
user = User.objects.first()
user.post_set.all() # get all Post objects that User is author in
user.events_attending.all() # get all related Attending objects
For more check Django Docs.
The Viewset def list looks like this:
class ThreeDimensionalModelViewSet(viewsets.ViewSet):
serializer_class = ThreeDimensionalModelSerializer
queryset = ThreeDimensionalModel.objects.all()
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def list(self, request):
models = ThreeDimensionalModel.objects.all()
serializer = ThreeDimensionalModelSerializer(models, many=True)
print(request.user.id)
return Response(serializer.data)
The serializer looks like this:
class ThreeDimensionalModelSerializer(serializers.ModelSerializer):
class Meta:
model = ThreeDimensionalModel
fields = ['File', 'Uploaded', 'Owner', 'Previous', 'SharedWithUser']
read_only_fields = ['Owner']
The model looks like this:
class ThreeDimensionalModel(models.Model):
File = models.FileField(upload_to='models')
Owner = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True, related_name='Owner')
Uploaded = models.DateTimeField(auto_now_add=True)
Previous = models.ForeignKey("self", on_delete=models.SET_NULL, default=None, null=True)
SharedWithUser = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='SharedWithUser')
When a user requests models at /api/models it should only show the models that are the same owner Id as his.
If no additional data is sent with that request then obviously you can't filter by user.
The straightforward way to do it is that for logged in users the cookie will contain user information such as userId.
When your endpoint recognizes the user who made the requested is logged in, it will use that as the filter for the query instead of all() as seen in the Django docs
https://docs.djangoproject.com/en/3.2/topics/db/queries/#retrieving-specific-objects-with-filters
To summarize - if the user is not logged in (or supplies the information as part of the request in some way) then the request is anonymous and there is no way to know who made it
UPDATE Issue was that I was not correctly applying migrations after changing the model to allow null/blank values.
Original Post
I'm using Django-rest-framework to enable tagging data during playback of Vimeo videos. I've built an endpoint at /watchtape/videotojam/ and am using jquery to post to the endpoint using the current time of the video as the start_time VideoToJam field.
I'm getting an django.db.utils.IntegrityError: player_list_videotojam.video_id may not be NULL
exception thrown when the view calls serializer.save(), even though serializer.is_valid() returns True.
I'm not sure what I'm doing wrong, since the video foreign key relationship is created with blank=True, null=True and the serializer representation indicates that video is not a required field.
This seems to be related to IntegrityError in django rest framework
however the fix of adding video = serializers.PrimaryKeyRelatedField() to the serializer throws AssertionError: Relational field must provide aquerysetargument, or set read_only=True and when I set read_only=True the IntegrityError exception returns.
Any help would be greatly appreciated.
Serializer
class VideoToJamSerializer(serializers.ModelSerializer):
class Meta:
model = VideoToJam
fields = ('id', 'start_time', 'end_time', 'video', 'jam', 'timecode_url')
Model
class VideoToJam(models.Model):
start_time = models.CharField(max_length=200,
validators=[_timecode_validator])
end_time = models.CharField(max_length=200,
validators=[_timecode_validator],
blank=True)
video = models.ForeignKey(Video, blank=True, null=True)
jam = models.ForeignKey(Jam, null=True, blank=True)
timecode_url = models.URLField(max_length=255, blank=True)
class Video(models.Model):
SITES = (
('vimeo', '''http://vimeo.com'''),
('youtube', '''http://youtube.com'''),
('', 'unknown'),
)
url = models.URLField(max_length=255)
source = models.CharField(max_length=200)
site = models.CharField(max_length = 7, choices=SITES)
#URL for vimeo embed code
player_url = models.CharField(max_length=2000)
def get_absolute_url(self):
return(reverse("video_player", kwargs={'video_id':self.id}))
def __str__(self):
return("Video {0}".format(self.id))
View
#api_view(['GET', 'POST'])
def viewvideotojam_list(request):
if request.method == 'POST':
serializer = VideoToJamSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
videotojams = VideoToJam.objects.all()
serializer = VideoToJamSerializer(videotojams, many=True)
return Response(serializer.data)
Urls
urlpatterns = patterns('',
url(r'^videotojam/$', views.viewvideotojam_list, name='videotojam_list'),
)
Setting blank=True, null=True should be enough. The null=True causes a schema change, did you generate a migration for it?
If you're on Django prior to 1.7 and you're using South, you should run migrate and schemamigration.
If you're on Django 1.7 or greater, you should run migrate and makemigrations.
I am using Django REST Framework to provide an API for my mobile app. I need send as extra argument when I creating a new Device the email of his owner.
Actually I send a json similar to this:
{"os_type": "AND",
"token": "dfsdfdfsd",
"email": "sdfdfd#sdfs.com"
}
I need pass some data to the standard ModelViewSet and overrides a little part (extract the email of the owner and associate It with the Device recently created. The problem is that I don't know how to get the id of this new object.
I have this ModelViewSet for my Device model:
class DeviceViewSet(viewsets.ModelViewSet):
queryset = Device.objects.all()
serializer_class = DeviceSerializer
def create(self, request):
"""
Overrides the create method in order to get
extra params: the email of the device's owner.
When this is done, we pass the method to his parent.
"""
print "creating..."
created = super(DeviceViewSet, self).create(request)
print type(created).__name__
#[method for method in dir(created) if callable(getattr(created, method))]
return created
The "created" object is type Response, and that will render with all de info, but I would like to get the ID in a more elegant or right way.
And this is my Device model:
class Device(models.Model):
"""
iOS or Android device, where is installed the app
"""
ANDROID = 'AND'
IOS = 'IOS'
OS_DEVICES_CHOICES = (
(ANDROID, 'Android'),
(IOS, 'iOS'),
)
os_type = models.CharField(max_length=3, choices=OS_DEVICES_CHOICES)
token = models.CharField(max_length=1000)
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
I prefer don't add the field owner in my Device model, because I have already the Owner model that refers to Device:
class Owner(models.Model):
name = models.CharField(max_length=200, blank=True, null=True)
biography = models.TextField(max_length=1000, blank=True, null=True)
birthday = models.DateField(blank=True, null=True)
country = models.CharField(max_length=50, blank=True, null=True)
language = models.CharField(max_length=50, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
devices = models.ManyToManyField(Device)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return u'[{0}] {1}'.format(self.id, self.name)
def __unicode__(self):
return u'[{0}] {1}'.format(self.id, self.name)
How Can I resolve this problem?
You can perform actions after objects are created in Django REST Framework by overriding perform_create on your view.
class DeviceViewSet(viewsets.ModelViewSet):
queryset = Device.objects.all()
serializer_class = DeviceSerializer
def perform_create(self, serializer):
from rest_framework.exceptions import ValidationError
data = self.request.data
if "email" not in data:
raise ValidationError({
"email": "No owner email provided",
})
try:
owner = Owner.objects.get(email=data["email"])
except Owner.DoesNotExist:
return Response(
'No owner with the email ({0}) found'.format(email),
status=status.HTTP_406_NOT_ACCEPTABLE
)
device = serializer.save()
owner.devices.add(device)
By overriding perform_create instead of the create method on the view, you won't have to worry about any changes being made to the create method that you will be missing during upgrades. The perform_create method is the recommended hook, so you don't need to worry about that breaking.
I've also made a few changes to the checks that are being done before the device is created.
A ValidationError is being raised for the 400 error when the email is not passed in with the request. This will produce the same style error messages as other fields, and should be handled by the default exception handler.
The try...except has been limited to the DoesNotExist error that will be triggered if the user gives an invalid email that does not match an owner in the database. This will prevent you squashing edge cases that weren't considered, though the DoesNotExist and MultipleObjectsReturned exceptions are the only ones that you should really receive from a get call.
The error for an unknown email no longer includes the exception notice, the message that is there already should be fine.
Also, if there is a way to tie the current user making the request (provided in request.user) to the owner of the device being created (owner in this case), you might want to skip them providing the email. This depends on how the API is supposed to function of course, because you might be interested in allowing users to tie devices to another owner's account.
Finally I solved the problem writing my own code to save the serializer object and getting directly the ID.
This is the full code:
class DeviceViewSet(viewsets.ModelViewSet):
queryset = Device.objects.all()
serializer_class = DeviceSerializer
def create(self, request):
"""
Overrides the create method in order to get
extra params: the email of the device's owner.
When this is done, we pass the method to his parent.
"""
print "creating..."
data = request.data
email = None
if 'email' in data:
email = data['email']
else:
return Response("No owner email provided", status=status.HTTP_400_BAD_REQUEST)
try:
owner = Owner.objects.get(email=email)
except Exception as e:
print e
return Response('No owner with the email ({0}) found, error: {1}'.format(email, e),
status=status.HTTP_406_NOT_ACCEPTABLE)
serializer = DeviceSerializer(data=request.data)
if serializer.is_valid():
device = serializer.save()
print device.id
owner.devices.add(device)
#device.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
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!