When I try to send pk2 or any other argument, it raises an AssertionError.
What I mean is this that the url
path('grade/<str:pk>/', IndividualGrade.as_view(), name="get-grade")
doesn't throw an error while the one below causes an error:
path('grade/<str:pk2>/', IndividualGrade.as_view(), name="get-grade")
My view is fairly simple as below:
class IndividualGrade(generics.RetrieveUpdateDestroyAPIView):
''' PUT/GET/DELETE grade/{grade:pk}/ '''
queryset = Grade.objects.all()
serializer_class = GradeSerializer
def put(self, request, *args, **kwargs):
try:
g1 = Grade.objects.get(grade=kwargs["pk"])
serializer = GradeSerializer(g1, data=request.data)
flag = 0
except Grade.DoesNotExist: # Create a new grade if a grade doesn't exist
g1 = Grade.objects.get(grade=kwargs["pk"])
serializer = GradeSerializer(g1, data=request.data)
flag = 1
if serializer.is_valid():
# call update/create here
else:
return Response(serializer.errors)
return Response(serializer.data, status=status.HTTP_200_OK)
I realized pk2 in the url works if I write my own get function (tried in another view), but I don't know how to fix this without writing my own get. While this have been discussed here. But I am still not sure how to fix it without writing my own get.
you need to add
lookup_field = 'pk2'
when you are using something else than pk, which is inbuilt for lookup . when you want something else in the url you need to mention that.
Related
I am working on a simple performance management system with react on frontend and django on the backend. They are supervisors who can give reviews to supervisees and supervisees can respond. I want all employees to receive email when they receive reviews from their supervisors and all the supervisors to receive email when their reviews are responded. For reviews and responses I am using two different serializers but same model.
Serializer for Response is:
class ResponseSerializer(serializers.ModelSerializer):
supervisor_name = serializers.SerializerMethodField('get_supervisor_name')
supervisor_email = serializers.SerializerMethodField('get_supervisor_email')
supervisee_name = serializers.SerializerMethodField('get_supervisee_name')
supervisee_email = serializers.SerializerMethodField('get_supervisee_email')
class Meta:
model = Review
fields = (
'id', 'review_text', 'response_text', 'date_of_review', 'date_of_response', 'supervisor', 'supervisor_name',
'supervisor_email', 'supervisee', 'supervisee_name', 'supervisee_email')
read_only_fields = ('review_text', 'date_of_review', 'supervisor', 'supervisee')
def get_supervisor_name(self, obj):
return obj.supervisor.first_name + " " + obj.supervisor.last_name
def get_supervisor_email(self, obj):
return obj.supervisor.email
def get_supervisee_name(self, obj):
return obj.supervisee.first_name + " " + obj.supervisee.last_name
def get_supervisee_email(self, obj):
return obj.supervisee.email
For sending mail I am using send_mail method from django.core And I am using Viewsets for Reviews and Responses.
Now Response operation will always be an update operation because Response will always be used to update existing Review object in which response_text field will be updated.
class ResponseViewSet(viewsets.ModelViewSet):
queryset = Review.objects.all()
permission_classes = [
# permissions.IsAuthenticated,
permissions.AllowAny,
]
serializer_class = ResponseSerializer
def update(self, request, *args, **kwargs):
serializer = ResponseSerializer(data=request.data)
if serializer.is_valid():
supervisor = serializer.data["supervisor_name"]
supervisee = serializer.data["supervisee_name"]
query = serializer.save()
mail_text = "Hi {}\n\nYou got a response for your 1:1 from {}.\n\nClick below to see the response:\n\n{}".format(
supervisor,
supervisee,
"https://example.com/#/pms/reviewsBySupervisor",
)
try:
if not settings.DEFAULT_EMAIL_RECIPIENTS:
settings.DEFAULT_EMAIL_RECIPIENTS.append(
str(serializer.data["supervisor_email"])
)
send_mail(
subject="New Response Received",
message=mail_text,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=settings.DEFAULT_EMAIL_RECIPIENTS,
fail_silently=False,
)
except (SMTPRecipientsRefused, SMTPSenderRefused):
LOGGER.exception("There was a problem submitting the form.")
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
So, the problem that I am facing is that when I try to send mail with update method in ResponseViewset as shown above. I get the following error:
Internal Server Error: /UMS/api/responses/38/
Traceback (most recent call last):
File "/home/shishir/Projects/performance_management/performance_management/reviews/serializers.py", line 77, in get_supervisor_name
return obj.supervisor.first_name + " " + obj.supervisor.last_name
AttributeError: 'NoneType' object has no attribute 'first_name'
So, what is happening is due to some reason, all the fields of that particular review are getting set to null as soon as I try to update and hence getting NoneType object has no attribute. I have checked in database table(MySQL), all the fields are getting set to null. Can anyone tell me why is this happening ? Where am I going wrong ? And what is the correct way to do it?
Finally I found my solution by changing the method update to partial_update. Apparently update method updates all the field while in above case I am attempting the field called response_text in the Review model which are setting other fields to null if they could be. Also after doing that I had to change the request from PUT to PATCH in frontend. Also I had to do some other minor coding changes like removing supervisor and supervisee fields from read_only_fields from ResponseSerializer. Updated code for ResponseViewset is shown below:
class ResponseViewSet(viewsets.ModelViewSet):
queryset = Review.objects.all()
permission_classes = [
permissions.IsAuthenticated,
#permissions.AllowAny,
]
serializer_class = ResponseSerializer
def partial_update(self, request,*args, **kwargs):
obj = self.get_object()
serializer = self.serializer_class(obj, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
mail_text = "Hi {},\n\nYou got a response for your 1:1 from {}.\n\nClick below to see the response:\n\n{}".format(
serializer.data["supervisor_name"],
serializer.data["supervisee_name"],
get_product_link("UMS/reviewsForDR"),
)
try:
if not settings.DEFAULT_EMAIL_RECIPIENTS:
settings.DEFAULT_EMAIL_RECIPIENTS.append(
# supervisor_email
serializer.data["supervisor_email"]
)
send_mail(
subject="New Response Received",
message=mail_text,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=settings.DEFAULT_EMAIL_RECIPIENTS,
fail_silently=False,
)
settings.DEFAULT_EMAIL_RECIPIENTS = []
except (SMTPRecipientsRefused, SMTPSenderRefused):
LOGGER.exception("There was a problem submitting the form.")
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I want to export my changelist (fields in list_display) as csv. I used the code from https://books.agiliq.com/projects/django-admin-cookbook/en/latest/export.html But it creates csv for the model fields. But in my case, I want to export changelist as csv, not the model fields.
Also, note that most of the fields in changelist(list_display) are calculated fields in Admin.
This is my code
class ExportCsvMixin:
def export_as_csv(self, request, queryset):
meta = queryset.model._meta
field_names = [field.name for field in meta.fields]
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
writer = csv.writer(response)
writer.writerow(field_names)
for obj in queryset:
row = writer.writerow([getattr(obj, field) for field in field_names])
return response
export_as_csv.short_description = "Export Selected"
class MyAdmin(admin.ModelAdmin, ExportCsvMixin):
list_display = ('field1',
'field2'
)
list_filter = ('field2')
actions = ["export_as_csv"]
def field1(self, obj):
<return logic here>
def field2(self, obj):
<return logic here>
NOTE:
field1 and field2 are calculated fields, and not model fields.
My model is a proxy model. But I don't think it would any difference in this case.
I want the csv to contain data for field1 and field2only, as they are in my changelist.
May be the trick would be to somehow point the queryset to that of changelist. but how do that ? Or if someone can suggest some other solution or even api to achieve this goal?
I had the same issue and I managed to hack it this way.
I looked into the ModelAdmin base class and I found the function responsible for handling actions methods, it's called response_action, I looked into it and changed the queryset it return what I need.
Let say you have a query set that return 'field1' and 'field2'.
Here is how I edited the function to return a custom queryset:
def response_action(self, request, queryset):
"""
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and
None otherwise.
"""
# There can be multiple action forms on the page (at the top
# and bottom of the change list, for example). Get the action
# whose button was pushed.
try:
action_index = int(request.POST.get('index', 0))
except ValueError:
action_index = 0
# Construct the action form.
data = request.POST.copy()
data.pop(admin.helpers.ACTION_CHECKBOX_NAME, None)
data.pop("index", None)
# Use the action whose button was pushed
try:
data.update({'action': data.getlist('action')[action_index]})
except IndexError:
# If we didn't get an action from the chosen form that's invalid
# POST data, so by deleting action it'll fail the validation check
# below. So no need to do anything here
pass
action_form = self.action_form(data, auto_id=None)
action_form.fields['action'].choices = self.get_action_choices(request)
# If the form's valid we can handle the action.
if action_form.is_valid():
action = action_form.cleaned_data['action']
select_across = action_form.cleaned_data['select_across']
func = self.get_actions(request)[action][0]
# Get the list of selected PKs. If nothing's selected, we can't
# perform an action on it, so bail. Except we want to perform
# the action explicitly on all objects.
selected = request.POST.getlist(admin.helpers.ACTION_CHECKBOX_NAME)
if not selected and not select_across:
# Reminder that something needs to be selected or nothing will
# happen
msg = _("Items must be selected in order to perform "
"actions on them. No items have been changed.")
self.message_user(request, msg, messages.WARNING)
return None
if not select_across:
##### change this line with your queryset that return field one and two
queryset = 'your_queryset_with_field'
response = func(self, request, queryset)
# Actions may return an HttpResponse-like object, which will be
# used as the response from the POST. If not, we'll be a good
# little HTTP citizen and redirect back to the changelist page.
if isinstance(response, HttpResponseBase):
return response
else:
return HttpResponseRedirect(request.get_full_path())
else:
msg = _("No action selected.")
self.message_user(request, msg, messages.WARNING)
return None
Check in this function the part I command with #####
redefine that function in the class that inherits from the model admin class
The function returns a custom queryset , now you can edit your export_as_csv_function according to that.
def export_as_csv(self, request, queryset):
field_names = ["field_one", "field_two"]
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(
'working_hours')
writer = csv.writer(response)
writer.writerow(field_names)
for obj in queryset:
writer.writerow([obj.get(field) for field in field_names])
return response
That it and you can go and download your CSV with the customs field.
I hope it's not too late and this helps other folks with the same issue.
I have written two class one for posting datas for payment and other one to show payment-successful message with order_id. I am sending order id from first function and i want to catch this id to show in my payment-successful template.
class ApiVIew(TemplateView):
template_name = 'payment.html'
def post(self,request):
r = requests.post(url='www.randomsite.com',params = {'authToken':'12345','card_no':'1234','card_cvv':'****'})
return HttpResponse(json.dumps({'response':r.json(),'status':'ok'}))
I call this class is ajax and parse there,so if r gives no error then i redirect(window.location=localhost:8000/success) to the success-payment.html page. so response gives me a json data:
{'isSuccess': 1, 'order_id': 1cq2,}
so i want to get this order_id and pass it to another function/class written below.
def payment_successfullView(request):
return render(request,'payment-successfull.html')
How can i achieve so? Thanks in advance.
1. Most simple way
urls.py:
...
path('<str:order_id>/success/', views.payment_successfullView, name='success'),
...
Views:
from django.shortcuts import redirect, reverse
class ApiVIew(TemplateView):
template_name = 'payment.html'
def post(self, request):
r = requests.post(url='www.randomsite.com',params = {'authToken':'12345','card_no':'1234','card_cvv':'****'})
if r.isSuccess:
return redirect(reverse('success', args=(r.order_id, )))
# do your stuff in case of failure here
def payment_successfullView(request, order_id):
return render(request,'payment-successfull.html', {
'order_id': order_id,
})
2. Another method using sessions:
urls.py:
...
path('success/', views.payment_successfullView, name='success'),
...
Views:
from django.shortcuts import redirect, reverse
from django.http import HttpResponseForbidden
class ApiVIew(TemplateView):
template_name = 'payment.html'
def post(self, request):
r = requests.post(url='www.randomsite.com',params = {'authToken':'12345','card_no':'1234','card_cvv':'****'})
if r.isSuccess:
request.session['order_id'] = r.order_id # Put order id in session
return redirect(reverse('success', args=(r.order_id, )))
# do your stuff in case of failure here
def payment_successfullView(request):
if 'order_id' in request.session:
order_id = request.session['order_id'] # Get order_id from session
del request.session['order_id'] # Delete order_id from session if you no longer need it
return render(request,'payment-successfull.html', {
'order_id': order_id,
})
# order_id doesn't exists in session for some reason, eg. someone tried to open this link directly, handle that here.
return HttpResponseForbidden()
Ok, I think the best answer points you in the right direction and let you figure out the fun part.
Tips:
Your APIView has to redirect to payment_successfullView
You have the order_id so you could use a DetailView
If you want to display a list of orders (order_id's) use ListView
I think using those tips, you'll be fine. Happy coding.
Note
You might want to read about Form views also, such view has an attribute called success_url. Ring a bell?
Am using codecoverage and it complains that I have 2 functions in 2 different class based views that are too similar.
Attached is the codecoverage error
Below are the code that were highlighted:
class PalletContentPickup(APIView):
"""
Picking up a pallet content to transfer to exiting pallet content
"""
def put(self, request, pk):
count = request.data['count']
pallet_id = request.data['pallet_id']
from_pallet_content = QuickFind.get_pallet_content_or_404(pallet_content_id=pk)
to_pallet = QuickFind.get_pallet_or_404(pallet_id=pallet_id)
Transfer.validate_if_can_pickup_pallet_content(from_pallet_content, to_pallet, request.user)
to_pallet_content = QuickFind.get_or_create_pallet_content(pallet=to_pallet, product=from_pallet_content.product)
ExitFormHelper.create_exit_form_line_item_on_pallet_content_if_no_exit_form_line(to_pallet_content, request.user)
Transfer.previous_pallet_content_to_new_pallet_content(from_pallet_content, to_pallet_content, count)
serializer = PalletSerializer(from_pallet_content.pallet)
return Response({"data": serializer.data}, status=status.HTTP_202_ACCEPTED)
class PalletContentPutback(APIView):
"""
Put back pallet content to an approved pallet
"""
def put(self, request, pk):
count = request.data['count']
pallet_id = request.data['pallet_id']
from_pallet_content = QuickFind.get_pallet_content_or_404(pallet_content_id=pk)
to_pallet = QuickFind.get_pallet_or_404(pallet_id=pallet_id)
Transfer.validate_if_can_putback_pallet_content(from_pallet_content, to_pallet, request.user)
to_pallet_content = QuickFind.get_or_create_pallet_content(pallet=to_pallet, product=from_pallet_content.product)
ExitFormHelper.create_exit_form_line_item_on_pallet_content_if_no_exit_form_line(to_pallet_content, request.user)
Transfer.previous_pallet_content_to_new_pallet_content(from_pallet_content, to_pallet_content, count)
serializer = PalletSerializer(from_pallet_content.pallet)
return Response({"data": serializer.data}, status=status.HTTP_202_ACCEPTED)
I read about strategy pattern in Python
Not sure if I should apply strategy pattern here and if so, how? Because the example in the url still does not help me realise exactly how to perform strategy pattern here.
As i see it there is only one line of difference between the two classes. so you can keep it really simple by
class PalletContent(object):
"""
Put back pallet content to an approved pallet
"""
def do_action(self, request, pk, action):
count = request.data['count']
pallet_id = request.data['pallet_id']
from_pallet_content = QuickFind.get_pallet_content_or_404(pallet_content_id=pk)
to_pallet = QuickFind.get_pallet_or_404(pallet_id=pallet_id)
if action == 'putback':
Transfer.validate_if_can_putback_pallet_content(from_pallet_content, to_pallet, request.user)
else:
Transfer.validate_if_can_pickup_pallet_content(from_pallet_content, to_pallet, request.user)
to_pallet_content = QuickFind.get_or_create_pallet_content(pallet=to_pallet, product=from_pallet_content.product)
ExitFormHelper.create_exit_form_line_item_on_pallet_content_if_no_exit_form_line(to_pallet_content, request.user)
Transfer.previous_pallet_content_to_new_pallet_content(from_pallet_content, to_pallet_content, count)
serializer = PalletSerializer(from_pallet_content.pallet)
return Response({"data": serializer.data}, status=status.HTTP_202_ACCEPTED)
class PalletContentPickup(APIView, PalletContent):
def put(self,request,pk):
self.do_action(request,pk,'pickup')
class PalletContentPutback(APIView, PalletContent):
def put(self,request,pk):
self.do_action(request,pk,'putback')
You are saving only a few lines of code but it may be worth it when it comes to maintenance. At the same time your validate methods do not seem to return anything. Are they raising exceptions?
I am writing an app in django rest-framework:
My views.py:
class tagList(generics.ListCreateAPIView,APIView):
model = tags
serializer_class = getAllTagsDetailSerializer
def get_queryset(self):
print "q1"
print self.request.QUERY_PARAMS.get('tag', None)
print self.request.user
print "q1"
if tags.objects.filter(tag='burger')!= None:
return tags.objects.filter(tag='burger')
else:
content = {'please move along': 'nothing to see here'}
return Response(content, status=status.HTTP_404_NOT_FOUND)
I want to return error status code if query returns None.
But the problem if i try to set Response it throws error:
Exception Type: TypeError
Exception Value:
object of type 'Response' has no len()
Exception Location: /usr/local/lib/python2.7/dist-packages/django/core/paginator.py in _get_count, line 53
Else if query result is Not None it is working.
How can i set status code on Django rest-framework.
The method is expected to return a QuerySet, not a Response object, my bet is that you should throw an Exception, either an APIException or an Http404.
Anyway your handling seems odd, I think you should just return the QuerySet and the framework will handle if the result is empty or not. The method should look like this:
def get_queryset(self):
return tags.objects.filter(tag='burger')
Can you try this
model = tags # Model name
serializer_class = getAllTagsDetailSerializer # Call serializer
def get_queryset(self):
key = self.request.QUERY_PARAMS.get('appKey', None)
getTagName = self.request.QUERY_PARAMS.get('tagName')
keyData = app.objects.filter(appKey=key).exists()
try:
if keyData == True:
return tags.objects.filter(tag=getTagName)
else:
raise exceptions.PermissionDenied
except app.DoesNotExist:
pass
I think it will work....