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)
Related
I have a problem for block access to not authorized user in pages dedicated to add new objects. List of that users is stored in many-to-many field in project object and in foreign key field.
Below is models.py
class Project(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="projects_as_owner", null=True)
project_managers = models.ManyToManyField(User, related_name="projects_as_pm", blank=True)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
date_of_insert = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Milestone(models.Model):
project_fk = models.ForeignKey(Project, related_name="milestones", on_delete=models.CASCADE)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
date_of_insert = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
And views.py with class I have problem
class NewMilestone(LoginRequiredMixin, generic.CreateView):
model = Milestone
fields = ['name', 'description']
lookup_url_kwarg = 'p_id'
template_name = 'main/new_milestone.html'
# ... two functions, that work good, not important here ...
def get_queryset(self):
qs = super(NewMilestone, self).get_queryset()
project = Project.objects.get(id=self.kwargs['p_id'])
if(qs.filter(project_fk__owner=self.request.user).exists() or User.objects.filter(id=self.request.user.id).filter(projects_as_pm__id=project.id).exists()):
return qs
else:
return Http404("You are not authenticated to this action")
Objective here is here to allow authenticated users (owner and project manager/s) to enter this view and for anybody else show info about declined access.
Problem is that, that method, get_queryset, doesn't block unauthorised users in CreateViev class.
I tried some configurations for that issue, every single one I used had this flaw.
My question here is how to make it work the way I expect from it?
PS. English is not my native language and it was a while since I wrote something, so please be understanding.
You are using the LoginRequiredMixin which is a good thing. But then you didn't set any of the parameters available.
LoginRequiredMixin inherits from AccessMixin and you can use all it's parameters with which it shouldn't be too complicated to cover your case.
Here's a possible implementation:
class NewMilestone(LoginRequiredMixin, generic.CreateView):
...
# your class attributes
...
raise_exception = True
# Returns a permission denied message. Default: empty string
def get_permission_denied_message(self):
return "Access is restricted to authenticated users"
If you have raise_exception set to True then the get_permission_denied_message method will be called. Otherwise the user will be redirected to the login_url which you also would have to declare as a class attribute.
So below I have some code that tests the functionality where someone creates a post and that post has a hash_tag which is "#video" in this case. The code takes the Post body and uses regex to find any word that starts with "#". If it does then it creates or gets that HashTag from the HashTag table. Then sets that list of HashTag to the hash_tags attribute under Post.
For some reason the CreatePostSerializer serializer is throwing an exception that doesn't make sense. The serializer is throwing the exception ValidationError({'hash_tags': [ErrorDetail(string='Invalid pk "[\'video\']" - object does not exist.', code='does_not_exist')]}). The reason this doesn't make sense is because when I debug and set a breakpoint right after except Exception as e under views.py this is what I get
>>>e
ValidationError({'hash_tags': [ErrorDetail(string='Invalid pk "[\'video\']" - object does not exist.', code='does_not_exist')]})
>>>HashTag.objects.get(pk='video')
<HashTag: HashTag object (video)>
>>>request.data['hash_tags']
['video']
So the >>> represents what I input into the debugger. I'm essentially stopped at the line return Response... and we can see e is the ValidationError I mentioned, but we can see that the object it claims doesn't exist does indeed exist. Why is the serializer throwing a "ValidationError - object does not exist" when it does?
Note: I have another test that does exactly the same thing and passes except no video file is being passed this leads me to believe that Django is doing something different in the case that the incoming body is multi-part. I also tried in the instance that there is only one hash tag to set hash_tags=<single hash tag> rather than a list and it worked. This is a hack though and cleaner solution is preferred.
helpers.py
import re
def extract_hashtags(text):
regex = "#(\w+)"
return re.findall(regex, text)
test.py
def test_real_image_upload_w_hash_tag(self):
image_file = retrieve_test_image_upload_file()
hash_tag = 'video'
response = self.client.post(reverse('post'),
data={'body': f'Some text and an image #{hash_tag}',
'images': [image_file]},
**{'HTTP_AUTHORIZATION': f'bearer {self.access_token}'})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
views.py
def set_request_data_for_post(request, user_uuid: str):
request.data['creator'] = user_uuid
post_text = request.data['body']
hash_tags_list = extract_hashtags(post_text)
hash_tags = [HashTag.objects.get_or_create(hash_tag=ht)[0].hash_tag for ht in hash_tags_list]
if len(hash_tags) > 0:
request.data['hash_tags'] = hash_tags
return request
def create_post(request):
user_uuid = str(request.user.uuid)
request = set_request_data_for_post(request=request, user_uuid=user_uuid)
try:
serializer = CreatePostSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
post_obj = serializer.save()
except Exception as e:
return Response(dict(error=str(e),
user_message=error_message_generic),
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.py
from rest_framework import serializers
from cheers.models import Post
class CreatePostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('creator', 'body', 'uuid', 'created', 'updated_at', 'hash_tags')
model.py
class Post(models.Model):
# ulid does ordered uuid creation
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)])
hash_tags = models.ManyToManyField(HashTag, blank=True)
class HashTag(models.Model):
hash_tag = models.CharField(max_length=HASH_TAG_MAX_LEN, primary_key=True, validators=[
MinLengthValidator(HASH_TAG_MIN_LEN)])
under your test/__init__.py you have to add these lines
from django.db.backends.postgresql.features import DatabaseFeatures
DatabaseFeatures.can_defer_constraint_checks = False
There's some weird internal bug where if you operate on one table a lot with a lot of different TestCase classes then it'll do a DB check at the end after it's been torn down and it'll cause an error.
I'm also using factory boy (https://factoryboy.readthedocs.io/en/stable/orms.html) to generate my test DB, which is the main reason this issue arises. The reason I believe this is because I switched out factory boy for just using <model>.objects.create() and my tests stopped failing.
so I'm trying to build a Ledger App. When new users sign-ups, they create a new Business account that is linked to them (ForeignKey). Here is my model:
User = get_user_model()
class Business_Account(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
business_name = models.CharField(max_length=50)
Type_of_service = models.CharField(choices=SERVICE_CATEGORIES, max_length=50)
business_email_address = models.EmailField(max_length=254)
class Meta:
verbose_name = "Business_Account"
verbose_name_plural = "Business_Accounts"
def __str__(self):
return self.business_name
Now, I want to make a get request view that can query and returns a business account that belongs to a particular user. My current view just returns all the business accounts available in the database irrespective of which user is login. Here is my view:
class AddBusinessAcctView(APIView):
def get_object(self):
try:
return Business_Account.objects.all()
except:
raise status.HTTP_404_NOT_FOUND
def get(self,request):
queryset = self.get_object()
serializer = BusinessAcctSerializer(queryset, many=True)
return Response(data=serializer.data, status=status.HTTP_200_OK)
Now, How can I query business accounts that belong to a particular user?. Thanks in advance
Normally, you'd have authentication and a logged-in user that you were trying to retrieve information for. Assuming you're going to do this, that user is available in the request object.
Business_Account.objects.get(user=request.user)
But in general, you can just do:
user = User.objects.get(username='john')
Business_Account.objects.get(user=request.user)
# or
Business_Account.objects.get(user_id=123456)
I finally got it. It is something similar to the above answer.
queryset = Business_Account.objects.filter(user=request.user)
I am developing a system where a user logs in and they can use the features of our service. We have used Django Authentication to make the User and Login backend.
This is the custom User model in my models.py -
class UserDef(AbstractUser):
def __str__(self):
return self.first_name + self.last_name
employeeId = models.IntegerField(unique=True)
email_address = models.EmailField()
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
USER_TYPES = [('1',"Admin"), ('2',"Director"), ('3',"Sr. Manager"), ('4',"Manager") , ('5',"Sr. Engineer"), ('6',"Assistant Regional Manager"), ('7',"Assistant to the Regional Manager")]
user_type = models.CharField(max_length=2048, choices=USER_TYPES)
created_at = models.DateTimeField(auto_now_add=True)
This in itself works really well and I was able to access all data as needed. I am writing my front-end in React though. Which means there's a certain disconnect between the templates html file and the actual React.js code. Which means I can't simply send the employeeId or some other attribute as context. Does anyone know how I can extract that User information from the Django DB (which we have connected to an RDS-MySQL server) and display it in my React app.
If the user is already logged in and a sessionID has been generated. You could just create an endpoint through where you can fetch the current user and then make a call to the endpoint in your React frontend.
#api_view(['GET'])
def current_user(request):
user = request.user
return Response({
'username' : user.username,
'firstname' : user.first_name,
'employeeID' : user.employeeId,
# and so on...
})
You could obviously, always use a serializer to ease the process.
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.