I can't figure out why my redirection after paying doesn't work.
I'm trying to display the thank_you template once the user has paid via Paypal. My code all works other than the final render of the thank_you template (I receive the email and see the 'Made it' print statement).
Urls:
from django.urls import path
from . import views
app_name = 'checkout'
urlpatterns = [
path('', views.checkout, name='checkout'),
path('thank_you', views.thank_you, name='thank_you'),
path('order_success', views.order_success, name='order_success'),
]
Views.py:
import json
from django.shortcuts import render, redirect, reverse, HttpResponseRedirect
from django.http import JsonResponse
from django.contrib import messages
from django.urls import reverse
from profiles.models import UserProfile
from products.models import Product
from django.views.decorators.http import require_POST
from .models import Order, OrderDetail
from django.core.mail import send_mail
from the_rescuers.settings import DEFAULT_FROM_EMAIL
from templated_email import send_templated_mail
from .forms import OrderForm
def checkout(request):
bag = request.session.get('bag', {})
if not bag:
messages.error(request, "There's nothing in your bag at the moment")
return redirect(reverse('products:products_list'))
order_form = OrderForm()
bag_products = []
for item_id, quantity in bag.items():
product = Product.objects.get(pk=item_id)
name = product.name
id = product.id
bag_products.append({'name': name, 'id': id, 'quantity': quantity})
bag_products = json.dumps(bag_products)
# Attempt to prefill the form with any info the user maintains in
# their profile
if request.user.is_authenticated:
profile = UserProfile.objects.get(user=request.user)
order_form = OrderForm(initial={
'first_name': profile.default_first_name,
'last_name': profile.default_last_name,
'email': profile.default_email,
'phone_number': profile.default_phone_number,
'country': profile.default_country,
'postcode': profile.default_postcode,
'city': profile.default_city,
'street_address_1': profile.default_street_address_1,
'street_address_2': profile.default_street_address_2,
'county': profile.default_county,
})
template = 'checkout/checkout.html'
success_url = '/checkout/order_success'
thank_you = '/checkout/thank_you'
context = {
'order_form': order_form,
'success_url': success_url,
'bag_products': bag_products,
'thank_you': thank_you,
}
return render(request, template, context)
def order_success(request):
"""
View that creates a new object with the JSON data, then redirects to the
thankyou page.
"""
# Take the request, decode it, split it into bag_contents and order_data
# and use this data to create a new order
request2 = request.body
my_json = request2.decode('utf8').replace("'", '"')
json_data = json.loads(my_json)
bag_contents = json_data.get('bagContents')
bag_contents = json.loads(bag_contents)
order_data = json_data.get('jsonData')
order_data = json.loads(order_data)
# Manually fill the user_id field with the user's id
order_data["user_id"] = request.user.id
# Remove the csrf token from the data
order_data.pop("csrfmiddlewaretoken", None)
# Create a new instance of the Order model using the order_data received
order = Order.objects.create(**order_data)
order.save()
# Loop through the bag_contents and save the details in OrderDetail model
for item in bag_contents:
product = Product.objects.get(pk=item['id'])
order_detail = OrderDetail(order=order, product=product,
quantity=item['quantity'])
order_detail.save()
order.update_total()
# Create a value to check in the thank_you view
request.session['redirected_from_order_success'] = True
print("Original: ", request.session)
# Send email to the provided email address
send_templated_mail(
template_name='order_confirmation',
from_email=DEFAULT_FROM_EMAIL,
recipient_list=[order.email],
context={'name': order.first_name,
'order_number': order.order_number,
'order_total': order.order_total,
},
)
return HttpResponseRedirect(reverse('checkout:thank_you'))
def thank_you(request):
"""
View that displays the thankyou page after processing an order.
"""
# Redirect to the custom 404 page if trying to access the page without
# making an order
if request.session.get('redirected_from_order_success'):
# Clear the bag and redirection token now that the order has been
# created
request.session.pop('bag', None)
request.session['redirected_from_order_success'] = False
print("Made it: ", request.session)
return render(request, 'checkout/thank_you.html')
else:
print("Diverted it: ", request.session)
return render(request, "404.html")
Relevant Checkout.html Javascript:
function completeOrder(){
let url = '{{ success_url }}'
const request= fetch(url, {
method: 'POST',
headers:{
'Content-type':'application/json',
'X-CSRFToken': csrftoken,
},
body:JSON.stringify({"bagContents": bagContents, "jsonData": jsonData} )
})
}
onApprove: (data, actions) => {
return actions.order.capture().then(function (orderData) {
const transaction = orderData.purchase_units[0].payments.captures[0];
return completeOrder()})
}
What's confusing is that the GET request for the thank_you template is made and gives a 200, it just doesn't move from the checkout page?
[24/Jan/2023 08:43:02] "POST /checkout/order_success HTTP/1.1" 302 0
Made it: <django.contrib.sessions.backends.db.SessionStore object at 0x7f75867dcb50>
[24/Jan/2023 08:43:03] "GET /checkout/thank_you HTTP/1.1" 200 6287
Any help would be much appreciated!
Related
I solved the problem while writing this question but I wanted to post it so maybe someone needs this answer
Hello my friends.
i am new to django testing.
while i'm testing my views i faced this error in some views.
This is my views.py:
def all_programs(request):
programs = Program.objects.all()
return render(request, 'orders/all_programs.html', {'programs': programs})
def checkout(request, slug):
if request.method == 'POST':
# get data from form and save it
program = get_object_or_404(Program, slug=slug)
dates = ProgramDate.objects.filter(program=program)
return render(request, 'orders/checkout.html', {'program': program, 'dates': dates})
This is urls.py:
from django.urls import path
from django.views.generic import RedirectView
from .views import *
app_name = 'orders'
urlpatterns = [
path('', RedirectView.as_view(url='https://www.another-website.net')),
path('tests/', all_programs, name='all_programs'),
path('checkout/<str:slug>/', checkout, name='checkout'),
path('checkout/return_page/', ReturnPage.as_view(), name='return_page'),
]
And this is test_views.py:
from django.test import TestCase
from django.shortcuts import reverse
class TestViews(TestCase):
def test_all_programs(self):
response = self.client.get(reverse('orders:all_programs'))
self.assertTemplateUsed(response, 'orders/all_programs.html')
def test_checkout(self): # error id here
response = self.client.get(reverse('orders:all_programs', kwargs={'slug': 'test'})) # I tried this
# response = self.client.get('http://127.0.0.1:8000/checkout/test/') #and this
self.assertTemplateUsed(response, 'orders/checkout.html')
The solution in this case is:
The test in Django does not use the default database but rather creates its own database that does not have any records (I completely forgot that), so you must create records before you start tests that relate to the database.
in this case i must create new program before test:
class TestViews(TestCase):
_program = {
'name': 'test_program',
'price': 1000,
'abbreviation': 'test',
'description': 'test_program',
'slug': 'test_program',
'id_from_zoho': 1000,
}
def test_checkout(self):
program = Program(**self._program)
program.save()
response = self.client.get(reverse('orders:checkout', kwargs={'slug': program.slug}))
self.assertTemplateUsed(response, 'orders/checkout.html')
Need help with a Django related doubt.
I've been trying to develop an ecommerce webapp using Django. When I try to get the session data after clicking on 'Add to Basket' it shows the response from the 'init' method and not the 'add' method.
This is the button:
<button type="button" id="add-button" value="{{product.id}}" class="btn btn-secondary btn-sm">Add to Basket</button>
Ajax Script:
<script>
$(document).on('click', '#add-button', function (e) {
e.preventDefault();
$.ajax({
type: 'POST',
url: '{% url "basket:basket_add" %}',
data: {
productid: $('#add-button').val(),
csrfmiddlewaretoken: "{{csrf_token}}",
action: 'post'
},
success: function (json) {
},
error: function (xhr, errmsg, err) {}
});
})
</script>
View.py file:
from django.shortcuts import render
from django.shortcuts import get_object_or_404
from django.http import JsonResponse
from store.models import Product
from .basket import Basket
def basket_summary(request):
return render(request, 'store/basket/summary.html')
def basket_add(request):
basket = Basket(request)
if request.POST.get('action') == 'post':
product_id = int(request.POST.get('productid'))
product = get_object_or_404(Product, id=product_id)
basket.add(product=product)
response = JsonResponse({'test':'data'})
return response
urls.py file:
from django.urls import path
from . import views
app_name = 'basket'
urlpatterns = [
path('', views.basket_summary, name='basket_summary'),
path('add/', views.basket_add, name='basket_add'),
]
Basket Class:
class Basket():
def __init__(self, request):
self.session = request.session
basket = self.session.get('skey')
if 'skey' not in request.session:
basket = self.session['skey'] = {}
self.basket = basket
def add(self, product):
product_id = product.id
if product_id not in self.basket:
self.basket[product_id] = {'price': str(product.price)}
self.session.modified = True
context_processors.py
from .basket import Basket
def basket(request):
return {'basket': Basket(request)}
Also added this to the templates.
When I try to decode it
s= Session.objects.get(pk='uomjko48iiek9er7jw1gah2ayrvoc5pv')
s.get_decoded()
I get {'skey': {}} and not the product ID and the price.
I have an users endpoint in drf.
I want the lookup_field for retrieving user to be something like #username not username.
How can I implement this?
i just solved it by creating a custom router.
in routers.py:
from rest_framework.routers import Route, SimpleRouter
class UserRouter(SimpleRouter):
"""
A router for user to support #username lookup.
"""
routes = [
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={
'get': 'list',
'post': 'create',
},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
Route(
url=r'^{prefix}/#{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Detail'}
),
]
then in urls.py:
from .routers import UserRouter
from .apis import UserViewSet
from django.urls import path, include
router = UserRouter()
router.register("", UserViewSet, basename="user")
urlpatterns = [
path("/", include(router.urls)),
]
now you can get user with #username lookup for example in my case:
http://127.0.0.1:8000/api/v1/accounts/#mojixcoder/
You can do something like this:
>>> from django.contrib.auth import get_user_model
>>> User = get_user_model()
>>> Users = User.objects.all()
>>> users_list = list()
>>> for user in Users:
... tmp = '#' + str(user)
... users_list.append(tmp)
>>> users_list
['#a', '#admin', '#test23', '#test24']
You can put this code line by line in your view,
if you are using DRF then just return users_list as response
return Response(users_list, {"Message": f"users list"}, status=HTTP_200_OK)
For filtering the users_list on the frontend and making it more interactive;
VanillaJS add event listener to the lookup field. Filter the result.
React, add a two states one for your results users, the other for lookup input,
quick bruteforce search algorthim I can think of is this:
keep in mind this is slow !
handleFindUser = (users, searchedUser) => {
let sr = [];
for(let i = 0; i < users.length; i++){
if(users[i].includes(searchedUser)) {
sr.push( users[i].username, searchedUser)
};
};
}, 1000)
return sr;
};
[EDIT]
Docs
https://www.django-rest-framework.org/api-guide/filtering/
Filter users by url:]
in api/views/
class UsersLookup(generics.ListAPIView):
serializer_class = Users
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
username = self.kwargs['username']
return Users.objects.filter(User__username=username)
in api/urls.py
re_path('^users/(?P#<username>.+)/$', UsersLookup.as_view()),
I have made an small weather web app in django & it is working properly but
when a wrong city name is entered it start showing KeyError page.
from django.shortcuts import render, redirect
from django.contrib import messages
import requests
#search page
def search(request):
return render(request, 'index.html')
#forecast result page
def forecast(request):
c = request.POST['city']
url = 'http://api.openweathermap.org/data/2.5/weather?q={}&appid=7fee53226a6fbc936e0308a3f4941aaa&units=metric'.format(c)
r = requests.get(url)
data = r.json()
weather = {
'description': data['weather'][0]['description'],
'icon': data['weather'][0]['icon'],
'city': c.title(),
'temperature': data['main']['temp']
}
print(r)
return render(request, 'weather.html', {'weather': weather})
On entering wrong city name it is giving KeyError,So I want that instead of giving KeyError django will redirect it to my homepage i.e index.html with an error message below it.
The API will tell you if the city name is not valid.
r = requests.get(url)
if r.status_code == 404:
messages.add_message('City not found')
return redirect('home')
data = r.json()
...
First of all, please do not construct the querysetring yourself: querystrings can not include a lot of characters. You can use Django's QueryDict for that, like:
from django.http import QueryDict
qd = QueryDict(mutable=True)
qd.update(q=c, appid='7fee53226a6fbc936e0308a3f4941aaa', units='metric')
url = 'http://api.openweathermap.org/data/2.5/weather?{}'.format(qd.urlencode())
For cities like 'New York', it will encode this as:
>>> qd.urlencode()
'q=New+York&appid=7fee53226a6fbc936e0308a3f4941aaa&units=metric'
so it replaces the space with a +.
Furthermore you can use try-except here to redirect to a different page, like:
from django.http import QueryDict
from django.shortcuts import redirect
def forecast(request):
try:
city = request.POST['city']
except:
return redirect('name-of-some-view')
qd = QueryDict(mutable=True)
qd.update(q=city, appid='7fee53226a6fbc936e0308a3f4941aaa', units='metric')
url = 'http://api.openweathermap.org/data/2.5/weather?{}'.format(qd.urlencode())
try:
data = r.json()
weather = {
'description': data['weather'][0]['description'],
'icon': data['weather'][0]['icon'],
'city': c.title(),
'temperature': data['main']['temp']
}
except KeyError:
return redirect('name-of-some-view')
return render(request, 'weather.html', {'weather': weather})
You can use the Django Messages Framework [Django-doc] to show messages to a user.
I'm currently trying to write my unittest for a successfull password reset.
TLDR: I can't figure out how to handle the injection of the form data in the post request in the set-password view.
For context, let me give you my code first, since a line of code speaks more than 1000 words.
accounts/api/views.py:
class PasswordResetView(generics.GenericAPIView):
"""(POST) Expects email. Sends a password reset mail to email."""
permission_classes = (permissions.AllowAny,)
serializer_class = PasswordResetSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
user = User.objects.get(email=serializer.data['email'])
if user.is_active and user.has_usable_password():
send_password_reset_email(user, request)
return Response(
{"detail": "A password reset email has been send."},
status=status.HTTP_204_NO_CONTENT
)
else:
return Response(
{"detail": "This users password can't be reset."},
status=status.HTTP_406_NOT_ACCEPTABLE
)
accounts/api/urls.py:
url(r'^password_reset/$', views.PasswordResetView.as_view(),
name="password_reset"),
accounts/api/serializers.py:
class PasswordResetSerializer(serializers.Serializer):
email = serializers.EmailField()
def validate_email(self, value):
if User.objects.filter(email__iexact=value).exists():
return value
else:
raise serializers.ValidationError("Email not found.")
accounts/utils.py:
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.sites.shortcuts import get_current_site
from django.utils import six
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from templated_email import send_templated_mail
password_reset_token = PasswordResetTokenGenerator()
def build_email_context(user_obj, request):
current_site = get_current_site(request)
context = {
"name": "",
"domain": current_site.domain,
"uid": urlsafe_base64_encode(force_bytes(user_obj.pk)),
'token': password_reset_token.make_token(user_obj)
}
if user_obj.name != "":
context["name"]: " " + user_obj.name
return context
def send_password_reset_email(user_obj, request):
context = build_email_context(user_obj, request)
send_templated_mail(
template_name="password_reset",
from_email="noreply#ordersome.com",
recipient_list=[user_obj.email],
context=context
)
The views that are linked to the route in the email are the standard CBV PasswordResetConfirmView and PasswordResetDoneView from django.contrib.auth.
And here is the test I wrote for this password reset sequence:
api/tests/test_views.py:
from django.contrib.auth import get_user, get_user_model
from django.core import mail
from rest_framework import status
from rest_framework.authtoken.models import Token
from rest_framework.reverse import reverse as api_reverse
from rest_framework.test import APITestCase
User = get_user_model()
class UserAPIViewsTestCase(APITestCase):
def setUp(self):
User.objects.create_user(
email="testuser#test.com", password="test1234")
self.login_url = api_reverse("api:auth:login")
self.register_url = api_reverse("api:auth:register")
self.password_reset_url = api_reverse("api:auth:password_reset")
self.password_change_url = api_reverse("api:auth:password_change")
self.user_delete_url = api_reverse("api:auth:user_delete")
def test_api_auth_password_reset_success(self):
"""Test if a password reset successfully resets password."""
pre_user = User.objects.get(email="testuser#test.com")
self.assertEqual(pre_user.check_password("test1234"), True)
email_data = {
"email": "testuser#test.com",
}
email_response = self.client.post(
self.password_reset_url, email_data, format="json")
self.assertEqual(email_response.status_code,
status.HTTP_204_NO_CONTENT)
password_reset_data = {
"new_password1": "newtest1234",
"new_password2": "newtest1234"
}
password_reset_response = self.client.get(
mail.outbox[0].body[-94:-37], format="json", follow=True)
path = password_reset_response.request["PATH_INFO"]
done_url = "http://testserver" + path
password_reset_done_response = self.client.post(
done_url, password_reset_data, format="json")
post_user = User.objects.get(email="testuser#test.com")
self.assertEqual(post_user.check_password("newtest1234"), True)
The last self.assertEqual fails. But when I test the view manually, it works, so the code should be correct. How should I modify the test?
When I log out done_url it gives me http://testserver/auth/reset/MQ/set-password/, which should be the correct path.
And when I log out password_reset_done_response it says, that it is a TemplateResponse and the status_code is 200 (which should mean successful, but it's not). Also this response still seems to have the path /auth/reset/MQ/set-password/ which should not be the case anymore.
password_reset_data is not complete - there's no info to which user the new password should be applied.
from django.contrib.auth.tokens import default_token_generator
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
...
password_reset_data = {
'uid': urlsafe_base64_encode(force_bytes(pre_user.pk)),
'token': default_token_generator.make_token(pre_user),
'new_password1': 'newtest1234',
'new_password2': 'newtest1234',
}
Other suggestions
setUp(self) should call super().setUp()
Use self.assertTrue instead of self.assertEqual
post_user is not needed, use pre_user.refresh_from_db() before last assert