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.
Related
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!
VIEWS.PY
from django.shortcuts import render
from django.shortcuts import redirect
from django.urls import reverse
from django.http import HttpResponseRedirect
from django import forms
import markdown2
from . import util
class AddPageForm(forms.Form):
title = forms.CharField(max_length=20)
content = forms.CharField(widget=forms.Textarea(
attrs={
"class": "form-control",
"placeholder": "Tell us more!"
})
)
def add_page(request):
if request.method == "POST":
form = AddPageForm(request.POST)
entries = util.list_entries()
if form.is_valid():
title = form.cleaned_data['title']
content = form.cleaned_data['content']
util.save_entry(title, content)
for entry in entries:
if title.upper() == entry.upper():
return render(request, "encyclopedia/errorpage.html")
else:
return HttpResponseRedirect(reverse('encyclopedia:entrypage'))
else:
return render(request, "encyclopedia/addpage.html", {
"form": AddPageForm()
})
URLS.PY
app_name = "encyclopedia"
urlpatterns = [
path("", views.index, name="index"),
path("wiki/<str:title>", views.entry_page, name="entrypage"),
path("search", views.search, name="search"),
path("add_page", views.add_page, name="addpage"),
]
ADDPAGE.HTML
<form action="{% url 'encyclopedia:addpage' %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" class="btn btn-secondary">
</form>
LAYOUT.HTML
<div>
Create New Page
</div>
<div>
I have tried updating the urls and the views to this but i keep getting error responses
path("add_page/<str:title>", views.add_page, name="addpage"),
def add_page(request, title):
Please advise where this error response could be coming from as the above edits is what i saw in some other stackoverflow responses to clear the error but this didn't work for me.
Thank you
When you make a redirect to entrypage, you need to specify the title, so:
from django.shortcuts import redirect
def add_page(request):
if request.method == "POST":
form = AddPageForm(request.POST)
entries = util.list_entries()
if form.is_valid():
title = form.cleaned_data['title']
content = form.cleaned_data['content']
util.save_entry(title, content)
for entry in entries:
if title.upper() == entry.upper():
return render(request, "encyclopedia/errorpage.html")
# specify title ↓
return redirect('encyclopedia:entrypage', title=title)
# …
I would also strongly advise to make use of a database, and not fetch all entries from the utility: a database is optimized to search effective, whereas accessing the list of files takes linear time to search. If you define a database model, you can add db_index=True [Django-doc] to build an index which can boost searching enormously.
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()),
Using django-allauth, how do I obtain form errors via AJAX login?
I am using this view with my /signup/ url:
from allauth.account.views import SignupView
Conveniently, it accepts my Ajax call:
var signupForm = function(token) {
$.ajax({ url:"/signup/",
type:"POST",
data: {
"email": $('#user').val(),
"password1": $('#pass1').val(),
"password2": $('#pass2').val()
},
success: function (){
location.href = '/';
window.location.reload(true);
},
error:function (er) {
alert("signup failure...",er);
}
});
};
Inconveniently, if there is a problem with the form, it returns:
error 400: Bad Request
This gives me no idea why a login has failed. I would like to be able to convey to the user what happened. Was it a problem with her password? Was her email address already registered?
Can I accomplish this with all-auth, or do I need to write my own validation from javascript?
I could not find a supported solution, so I hacked it:
from django.http import JsonResponse
from allauth.account.views import SignupView, _ajax_response
class AjaxSignUpView(SignupView):
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
response = self.form_valid(form)
return _ajax_response(
self.request, response, form=form, data=self._get_ajax_data_if())
else:
return JsonResponse({'errors': form.errors})
The issue with your code is that it is missing the csrfmiddlewaretoken. You should include it in the form (with the tag {% csrf_token %}) and then add it to your AJAX request like this:
$.ajax({ url:"/signup/",
type:"POST",
data: {
"email": $('#user').val(),
"password1": $('#pass1').val(),
"password2": $('#pass2').val(),
"csrfmiddlewaretoken": $('<selector for the csrf token input>').val()
},
...
this item is required for all POST requests in django.
Is there a full tastypie django example site and setup available for download? I have been wrestling with wrapping my head around it all day. I have the following code. Basically, I have a POST form that is handled with ajax. When I click "submit" on my form and the ajax request runs, the call returns "POST http://192.168.1.110:8000/api/private/client_basic_info/ 404 (NOT FOUND)" I have the URL configured alright, I think. I can access http://192.168.1.110:8000/api/private/client_basic_info/?format=json just fine. Am I missing some settings or making some fundamental errors in my methods? My intent is that each user can fill out/modify one and only one "client basic information" form/model.
a page:
{% extends "layout-column-100.html" %}
{% load uni_form_tags sekizai_tags %}
{% block title %}Basic Information{% endblock %}
{% block main_content %}
{% addtoblock "js" %}
<script language="JavaScript">
$(document).ready( function() {
$('#client_basic_info_form').submit(function (e) {
form = $(this)
form.find('span.error-message, span.success-message').remove()
form.find('.invalid').removeClass('invalid')
form.find('input[type="submit"]').attr('disabled', 'disabled')
e.preventDefault();
var values = {}
$.each($(this).serializeArray(), function(i, field) {
values[field.name] = field.value;
})
$.ajax({
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(values),
dataType: 'json',
processData: false,
url: '/api/private/client_basic_info/',
success: function(data, status, jqXHR) {
form.find('input[type="submit"]')
.after('<span class="success-message">Saved successfully!</span>')
.removeAttr('disabled')
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR)
console.log(textStatus)
console.log(errorThrown)
var errors = JSON.parse(jqXHR.responseText)
for (field in errors) {
var field_error = errors[field][0]
$('#id_' + field).addClass('invalid')
.after('<span class="error-message">'+ field_error +'</span>')
}
form.find('input[type="submit"]').removeAttr('disabled')
}
}) // end $.ajax()
}) // end $('#client_basic_info_form').submit()
}) // end $(document).ready()
</script>
{% endaddtoblock %}
{% uni_form form form.helper %}
{% endblock %}
resources
from residence.models import ClientBasicInfo
from residence.forms.profiler import ClientBasicInfoForm
from tastypie import fields
from tastypie.resources import ModelResource
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import DjangoAuthorization, Authorization
from tastypie.validation import FormValidation
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
fields = ['username']
filtering = {
'username': ALL,
}
include_resource_uri = False
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
def dehydrate(self, bundle):
forms_incomplete = []
if ClientBasicInfo.objects.filter(user=bundle.request.user).count() < 1:
forms_incomplete.append({'name': 'Basic Information', 'url': reverse('client_basic_info')})
bundle.data['forms_incomplete'] = forms_incomplete
return bundle
class ClientBasicInfoResource(ModelResource):
user = fields.ForeignKey(UserResource, 'user')
class Meta:
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
include_resource_uri = False
queryset = ClientBasicInfo.objects.all()
resource_name = 'client_basic_info'
validation = FormValidation(form_class=ClientBasicInfoForm)
list_allowed_methods = ['get', 'post', ]
detail_allowed_methods = ['get', 'post', 'put', 'delete']
Edit:
My resources file is now:
from residence.models import ClientBasicInfo
from residence.forms.profiler import ClientBasicInfoForm
from tastypie import fields
from tastypie.resources import ModelResource
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import DjangoAuthorization, Authorization
from tastypie.validation import FormValidation
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
fields = ['username']
filtering = {
'username': ALL,
}
include_resource_uri = False
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
#def apply_authorization_limits(self, request, object_list):
# return object_list.filter(username=request.user)
def dehydrate(self, bundle):
forms_incomplete = []
if ClientBasicInfo.objects.filter(user=bundle.request.user).count() < 1:
forms_incomplete.append({'name': 'Basic Information', 'url': reverse('client_basic_info')})
bundle.data['forms_incomplete'] = forms_incomplete
return bundle
class ClientBasicInfoResource(ModelResource):
# user = fields.ForeignKey(UserResource, 'user')
class Meta:
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
include_resource_uri = False
queryset = ClientBasicInfo.objects.all()
resource_name = 'client_basic_info'
validation = FormValidation(form_class=ClientBasicInfoForm)
#list_allowed_methods = ['get', 'post', ]
#detail_allowed_methods = ['get', 'post', 'put', 'delete']
def apply_authorization_limits(self, request, object_list):
return object_list.filter(user=request.user)
I made the user field of the ClientBasicInfo nullable and the POST seems to work. I want to try updating the entry now. Would that just be appending the pk to the ajax url? For example /api/private/client_basic_info/21/? When I submit that form I get a 501 NOT IMPLEMENTED message. What exactly haven't I implemented? I am subclassing ModelResource, which should have all the ORM-related functions implemented according to the docs.
Okay, I figured it out. I wasn't being careful. The AJAX request type should have been "PUT" to handle the 501 not implemented error (I was performing an update). I have also set up a custom authentication class to handle 403 errors.