I'm working on small project using Django Rest Framework, i would like to delete multiple IDs but i get always an error when i send a delete request by sending IDs /1,2,3,4 as a string, i get id must be an integer.
this is my code,
class UpdateDeleteContact(APIView):
def get(self, request, pk):
contactObject = get_object_or_404(Contact, pk=pk)
serializeContactObject = ContactSerializer(contactObject)
return Response(serializeContactObject.data)
def delete(self, request, pk):
delete_id = request.get('deleteid', None)
if not delete_id:
return Response(status=status.HTTP_404_NOT_FOUND)
for i in delete_id.split(','):
get_object_or_404(User, pk=int(i)).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
can someone give me an example how to bulk delete
This code will enable you to send multiple ids through delete method and receive them as string.
path('url/<str:pk_ids>/', views.UpdateDeleteContact.as_view()),
#view.py
class UpdateDeleteContact(APIView):
def get(self, request, pk_ids):
ids = [int(pk) for pk in pk_ids.split(',')]
contactObject = Contact.objects.filter(id__in=ids)
serializeContactObject = ContactSerializer(contactObject, many=True)
return Response(serializeContactObject.data)
def delete(self, request, pk_ids):
ids = [int(pk) for pk in pk_ids.split(',')]
for i in ids:
get_object_or_404(User, pk=i).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
But for me it is not recommended since the defined url can interfere with other methods like retrieve.
Another solution that I can offer you is to enter a parameter in the url, let's call it "pk_ids", with the different ids separated by commas.
def delete(self, request, pk):
pk_ids = request.query_params.get('pk_ids', None)
if pk_ids:
for i in pk_ids.split(','):
get_object_or_404(User, pk=int(i)).delete()
else:
get_object_or_404(User, pk=pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
So you should call the url like
url.com/url/?pk_ids=1,2,3,4
Try with:
User.objects.filter(id__in=[int(x) for x in delete_id.split(',') if x]).delete()
New, more explained...
Your code doesn't work like a restful API... Delete is only for one object as a rule. You can read this thread
As Sohaib said in the comments, you can use the post function and retrieve id list from body, or as a string like your example.
Related
I'm a beginner to Django, i have written a class-based API view with mixin. the functionality is simple i.e fetch the data of the given id.Im pasting the code below.
class GenericAPi(generics.GenericAPIView,mixins.ListModelMixin,mixins.RetrieveModelMixin):
serializer_class=ArticleSerializer
queryset=Article.objects.all()
lookup_field="id"
def get(self,request,id):
if id:
data=self.retrieve(request)
return Response({"data":data.data,"status":data.status_code})
else:
return self.list(request)
this is the response I'm getting
{"id":5,"title":"loream","author":"me"}
then I navigate to the retrieve function in the mixin, to make some changes in the response.
def retrieve(self, request, *args, **kwargs):
print('Retrieving')
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response({"result":serializer.data})
and then I make a call to API, but still, I'm getting the same response.
How to customize the response in the retrieve function itself.
I need response like this.
{"result":{"id":5,"title":"loream","author":"ipsum"}}
I think you don't have to customize retrieve function in the RetrieveModelMixin.
class GenericAPi(generics.GenericAPIView,mixins.ListModelMixin,mixins.RetrieveModelMixin):
serializer_class=ArticleSerializer
queryset=Article.objects.all()
lookup_field="id"
def get(self, request, id):
if id:
try:
article = Article.objects.get(pk = id)
return Response({"result": ArticleSerializer(article).data})
except Article.DoesNotExist:
return Response(status = status.HTTP_404_NOT_FOUND)
return
return self.list(request, *args, **kwargs)
I have the following serializer setup to take in a postcode and return a list of service objects:
class ServiceSearchSerializer(Serializer):
area = CharField(max_length=16)
services = DictField(child=JSONField(), read_only=True)
def validate_area(self, value):
if re.match('^[A-Z]{1,2}[0-9][A-Z0-9]? ?([0-9][A-Z]{2})?$', value) is None:
raise ValidationError("This is not a valid postcode.")
And I tried to use this to create an API endpoint which out take in the area.
class ServiceSearchAPI(GenericAPIView):
serializer_class = ServiceSearchSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
area = serializer.data['area']
return Response(area)
However, when trying to get the area from the serializer, it returns None. But the value for request.data['area'] is correct. Does anyone know why this is happening?
I just checked the docs and they specify returning the value. Try returning it like this:
def validate_area(self, value):
if re.match('^[A-Z]{1,2}[0-9][A-Z0-9]? ?([0-9][A-Z]{2})?$', value) is None:
raise ValidationError("This is not a valid postcode.")
return value
I am working with a CBV that uses 2 ModelForm instances. I would like to display the individual form errors. It seems like this is a little challenging when using multiple forms in a class based view.
Heres a smaller snippet to show what I am working with...
class EmployeeCreate(CreateView):
form_class = EmployeeCreateForm
form_class_2 = AddressCreateForm
def post(self, request, *args, **kwargs):
employee_form = self.form_class(request.POST)
address_form = self.form_class_2(request.POST)
# Make sure both forms are validated
if employee_form.is_valid() and address_form.is_valid():
employee = employee_form.save(commit=False)
address = address_form.save(commit=False)
employee.parent = self.request.user
employee.save()
address.user = employee
address.save()
return JsonResponse({'message': 'Employee created successfully.'}, status=200)
else:
return self.form_invalid(**kwargs)
def get_context_data(self, **kwargs):
# render both forms to create an Account, and Address
context = super(EmployeeCreateView, self).get_context_data()
context['employee_form'] = self.form_class
context['address_form'] = self.form_class_2
return context
def form_invalid(self, **kwargs):
return JsonResponse({'success': False})
Now when the form is invalid, the form_invalid method is getting called and returning the JsonResponse message, but I would much rather return the specific form error.
I am trying to find a way to display each individual form error for the employee_form and the address_form. Is there a possible way to do this override in the form_invalid method?
Thank you in advance!
you are returning both forms error in single JsonResponse. Instead you should return different forms error in single JsonResponse like
return JsonResponse({'employee_form_errors': self.form_invalid(employee_form),
'address_form_errors': self.form_invalid(address_form) }, status=400)
you should use individually use form_invalid with both forms.
I want to send a login link to the users.
I know there are some OneTimePassword apps out there with thousands of features. But I just want some easy and barebon way to login user via login link.
My question is if this is a correct way to go about this. Like best practice and DRY code.
So I've set up a table that stores three rows.
1. 'user' The user
2. 'autogeneratedkey' A autogenerated key
3. 'created_at' A Timestamp
When they login, the'll be sent a mail containing a login link valid for nn minutes.
So the login would be something like
https://example.net/login/?username=USERNAME&autogeneratedkey=KEY
The tricky part for me is to figure out a good way to check this and log in the user.
I'm just guessing here. But would this be a good approach?
class login(generic.CreateView):
def get(self, request, *args, **kwargs):
try:
autgeneratedkey = self.request.GET.get('autgeneratedkey', '')
username = self.request.GET.get('username', '')
obj_key = Login.objects.filter(autgeneratedkey=autgeneratedkey)[0]
obj_user = Login.objects.filter(userusername=username)[0]
try:
if obj_user == obj_key: #Compare the objects if same
if datetime.datetime.now() < (obj_key.created_at + datetime.timedelta(minutes=10)): #Check so the key is not older than 10min
u = CustomUser.objects.get(pk=obj_user.user_id)
login(request, u)
Login.objects.filter(autgeneratedkey=autgeneratedkey).delete()
else:
return login_fail
else:
return login_fail
except:
return login_fail
return redirect('index')
def login_fail(self, request, *args, **kwargs):
return render(request, 'login/invalid_login.html')
It feels sloppy to call the same post using first the autogeneratedkey then using the username. Also stacking if-else feels tacky.
I would not send the username in the get request. Just send an autogenerated key.
http://example.com/login?key=random-long-string
Then this db schema (it's a new table because I don't know if Login is already being used.
LoginKey ( id [PK], user [FK(CustomUser)], key [Unique], expiry )
When a user provides an email, you create a new LoginKey.
Then do something like this:
def get(self, request, *args, **kwargs):
key = request.GET.get('key', '')
if not key:
return login_fail
login_key = LoginKey.objects.get(key=key)
if login_key is None or datetime.datetime.now() > login_key.expiry:
return login_fail
u = login_key.user
login(request, u)
login_key.delete()
return redirect('index')
Probably you can optimize the code like this:
First assuming you have relationship between User and Login Model like this:
class Login(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
Then you can use a view like this:
class LoginView(generic.View):
def get(self, request, *args, **kwargs):
try:
autgeneratedkey = self.request.GET.get('autgeneratedkey', '')
username = self.request.GET.get('username', '')
user = CustomUser.objects.get(login__autgeneratedkey=autgeneratedkey, username=username, login__created_at__gte=datetime.now()-datetime.timedelta(minutes=10))
login(request, user)
user.login_set.all().delete() # delete all login objects
except CustomUser.DoesNotExist:
return login_fail
return redirect('index')
Just another thing, it is not a good practice to use GET method where the database is updated. GET methods should be idempotent. Its better to use a post method here. Just allow user to click the link(which will be handled by a different template view), then from that template, use ajax to make a POST request to this view.
I am building a TemplateView with 2 forms, one to allow user to select the customer (CustomerForm) and another to add the order (OrderForm) for the customer.
Code:
class DisplayOrdersView(TemplateView):
template_name = 'orders/orders_details_form.html'
def get_context_data(self, **kwargs):
context = kwargs
context['shippingdetailsform'] = ShippingDetailsForm(prefix='shippingdetailsform')
context['ordersform'] = OrdersForm(prefix='ordersform')
return context
def dispatch(self, request, *args, **kwargs):
return super(DisplayOrdersView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
profile=request.user.get_profile()
if context['shippingdetailsform'].is_valid():
instance = context['shippingdetailsform'].save(commit=False)
instance.profile = profile
instance.save()
messages.success(request, 'orders for {0} saved'.format(profile))
elif context['ordersform'].is_valid():
instance = ordersform.save(commit=False)
shippingdetails, created = shippingdetails.objects.get_or_create(profile=profile)
shippingdetails.save()
instance.user = customer
instance.save()
messages.success(request, 'orders details for {0} saved.'.format(profile))
else:
messages.error(request, 'Error(s) saving form')
return self.render_to_response(context)
Firstly, I can't seem to load any existing data into the forms. Assuming a onetoone relationship between UserProfile->ShippingDetails (fk: UserProfile)->Orders (fk:ShippingDetails), how can I query the appropriate variables into the form on load?
Also, how can I save the data? It throws an error when saving and I have been unable to retrieve useful debug information.
Is my approach correct for having multiple forms in a templateview?
You're not passing the POST data into the forms at any point. You need to do this when you instantiate them. I would move the instantiation out of get_context_data and do it in get and post: the first as you have it now, and the second passing request.POST.
Also note that you probably want to check both forms are valid before saving either of them, rather than checking and saving each in turn. The way you have it now, if the first one is valid it won't even check the second, let alone save it, so you won't get any errors on the template if the first is valid but the second is invalid.