Django admin site change_list view customization - python

I have a ModelAdmin subclass for my gradeScalesSettings model:
#admin.register(gradeScalesSetting)
class gradeScalesSettingAdmin(admin.ModelAdmin):
list_display = ('configuration_select', 'NumberOfGrades', 'Rounding','Precision', 'Status',)
change_list_template = 'admin/Homepage/view.html'
Actual result
After I click Grade Scale Settings:
How to connect it to my views.py?
This is what I want to code in my views.py:
def gradescales(request):
gradeScalesSettings = gradeScalesSetting.objects.all()
configurations = configuration.objects.all()
rounding = gradeScalesSetting.objects.all().values_list('Rounding', flat=True).distinct()
print(rounding)
return render(request, 'Homepage/gradescale.html', {"rounding": rounding,"gradeScalesSetting":gradeScalesSettings,"configurations":configurations})
When I tried this:
#admin.register(gradeScalesSetting)
class gradeScalesSettingAdmin(admin.ModelAdmin):
def new_NumberOfGrades(self, obj):
if obj.NumberOfGrades == 'Grade Scale Settings':
return '<a href="view.html" </a>' # this url will redirect to your
In my ModelAdmin subclass:
list_display = ('configuration_select', 'new_NumberOfGrades', 'Rounding','Precision', 'Status',)
Is there any way to connect it to my views.py?
Expected result
This is what I want to show in my view.html:
That is why I want to connect it to my views.py.

Override ModelAdmin.changelist_view to set extra_context.
#admin.register(gradeScalesSetting)
class gradeScalesSettingAdmin(admin.ModelAdmin):
list_display = ('configuration_select', 'NumberOfGrades', 'Rounding','Precision', 'Status',)
change_list_template = 'admin/Homepage/view.html'
def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
gradeScalesSettings = gradeScalesSetting.objects.all()
configurations = configuration.objects.all()
rounding = gradeScalesSetting.objects.all().values_list('Rounding', flat=True).distinct()
extra_context.update({
"rounding": rounding,
"gradeScalesSetting": gradeScalesSettings,
"configurations": configurations,
})
return super().changelist_view(request, extra_context=extra_context)

You can alter routing for ModelAdmin by overriding get_urls:
class gradeScalesSettingAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super().get_urls()
pat = [i for i in urls if str(i.name).endswith('changelist')][0] # Find needed path object to replace
index = urls.index(pat)
urls[index] = path(pat.pattern._route, gradescales)
return urls
This way you can have full control of a view used for your admin page and even add additional pages (more than only changelist and edit views) as you want.
If you do not need additional default views like object edit page, you can simplify code above by replacing original urls instead of searching one needed and patching:
class gradeScalesSettingAdmin(admin.ModelAdmin):
def get_urls(self):
urls = [
path('', gradescales)
]
return urls

Related

How to generate Django / Python Test for view with LoginRequiredMixin and UserPassesTestMixin

I am trying for some time to test a view of mine that I have protected from access via LoginRequiredMixin and UserPassesTestMixin.
Unfortunately I do not manage to write the appropriate test.
here is the view. The special thing is, that the user must not only be logged in, but he must also belong to the group "Administrator" or "Supervisor".
With this combination I have not yet managed to write a test.
Please who can help me. Here is my View:
class FeatureListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = FeatureFilm
template_name = "project/feature-list.html"
def test_func(self):
if self.request.user.groups.filter(name="Administrator").exists():
return True
elif self.request.user.groups.filter(name="Supervisor").exists():
return True
elif self.request.user.groups.filter(name="Operator").exists():
return True
else:
return False
def handle_no_permission(self):
return redirect("access-denied")
and here is a snippet of the url.py:
urlpatterns = [
path("feature/list/", FeatureListView.as_view(), name="feature-list"),
path(
"feature/<int:pk>/date",
FeatureDetailViewDate.as_view(),
name="feature-detail-date",
),
how would you test this FeatureListView and the template belongs to
thank you very much!
First of all it would be better if you would give those groups a permission. Because django offers a lot nice functionalities to check for permissions rather than checking for groups (see here). You would give Administrator, Supervisor and Operator all the same permission and then instead of checking if the user is in one of these groups, you would just check whether the user has that permission. But as that was not the question there you go:
from django.test import TestCase
from django.test import Client
class TestFeatureListView(TestCase):
def _create_administrator(self):
u = Users.objects.create( <input for administrator> )
u.groups.add(administrator)
return u
def _create_supervisor(self):
u = Users.objects.create( <input for supervisor> )
u.groups.add(supervisor)
return u
def _create_operator(self):
u = Users.objects.create( <input for operator> )
u.groups.add(operator)
return u
def _create_normal_user(self):
return Users.objects.create( <input for normal_user> )
def test_users_with_access_rights(self):
users = [
self._create_administrator(),
self._create_supervisor(),
self._create_operator(),
]
for u in users:
c = Client() # initialize every iteration a new client
c.login(username=<username>, password=<password>)
response = c.get(reverse("appwithfeaturelist:feature-list"))
self.assertTemplateUsed(response, "project/feature-list.html")
def test_users_withOUT_access_rights(self):
u = self._create_normal_user()
self.client.login(username=<username>, password=<password>)
response = self.client.get(reverse("appwithfeaturelist:feature-list"), follow=True)
self.assertTemplateNotUsed(response, "project/feature-list.html")
self.assertRedirects(response, "/access-denied/")
def test_without_any_user(self):
response = self.client.get(reverse("appwithfeaturelist:feature-list"), follow=True)
self.assertTemplateNotUsed(response, "project/feature-list.html")
self.assertRedirects(response, "/access-denied/") # this is probably wrong and should test whether the redirect goes to something like a login page

How to call class method in Django URLs path?

How to call class method in Django URLs path?
I want to call data_display method inside DataLoad class. how can i call it in my URLs.py file? so when i hit the path then it will render to the data_display.html template.
views.py
class DataLoad:
def __init__(self, save_path, name_of_file):
self.save_path = save_path
self.name_of_file = name_of_file
def file_load(self):
file_path = os.path.join(self.save_path, self.name_of_file+".html")
return file_path
def data_display(request,*args, **kwargs):
df = pd.read_csv("/home/satyajit/Desktop/opensource/data/us_amz.csv", low_memory=False)
json_records = df.reset_index().to_json(orient ='records')
data = []
data = json.loads(json_records)
context = {'data': data}
return render(request, "home/data_display.html", context)
urls.py
from apps.home.views import DataLoad
data = DataLoad.data_display(request)
urlpatterns = [
#path('data_display', DataLoad.as_view(), name='data_display'),
path('data_display', data, name='data_display'),
]
According to the documentation, your view must extends the View class and your method should be name as the HTTP method you want to match.
from django.views import View
class DataLoad(View):
def get(request, **kwargs):
df = pd.read_csv("/home/satyajit/Desktop/opensource/data/us_amz.csv", low_memory=False)
# ...
return render(request, "home/data_display.html", context)
# url.py
from apps.home.views import DataLoad
urlpatterns = [
path('data_display', DataLoad.as_view(), name='data_display'),
]
Dependency injection
This part answer the OP comment.
If you want to have a better decoupling of you DataLoad from the request, you will need to split your view from your Dataload classes.
This is called dependency injection.
So you have your DataLoad classes that don't use any HTTP related things:
# file: core/dataload.py
class DataLoad:
def __init__(self, save_path, name_of_file):
self.save_path = save_path
self.name_of_file = name_of_file
def file_load(self):
file_path = os.path.join(self.save_path, self.name_of_file+".html")
return file_path
def data_display():
df = pd.read_csv("...us_amz.csv", low_memory=False)
json_records = df.reset_index().to_json(orient ='records')
return json.loads(json_records)
You then build a view that will use this class.
Your View depends on DataLoad.
You inject a DataLoad instance into your view.
Your DataLoad class is decoupled from your view.
# file: views.py
class DisplayDataView(View):
data_load_service = None
def __init__(self, data_load_service):
self.data_load_service = data_load_service
def get(self, request):
return render(request, 'home/data_display.html', {
'data': this.data_load_service.data_display(),
}
You then build your url passing a DataLoad instance to your view
# file: urls.py
from core.dataload import DataLoad
data_load_service = DataLoad(...)
urls = [
path(
'data-load',
DisplayDataView.as_view(
data_load_service=data_load_service,
),
name='data-load',
),
]
This is the basic idea. I would personaly find another place for the DataLoad instanciation but it should do the trick for you until you find out a better way to organize all this.
Main point here is decoupling what your business logic is from the HTTP part (ie: request/response) of your application.

Django REST: get_queryset providing duplicate responses

I am creating a simple online shop app so when you want to buy an item, a button click leads you to the charge api. (ex. item 2 will direct to /api/charge/2)
urls.py
from django.urls import path
from gallery.views import ClothingView
from gallery.api.views import ListClothes
from gallery.api.views import ChargeView
urlpatterns = [
path('api/<int:num>/', ListClothes.as_view()),
path('api/charge/<int:num>', ChargeView.as_view(), name='charge_api'),
]
views.py
class ChargeView(ListCreateAPIView):
serializer_class = ChargeSerializer
count = 0
def get_queryset(self):
a = ClothingModel.objects.filter(id=self.kwargs['num']).first()
net_price = int(float(a.full_price) * 100)
if float(a.discount) > 0.00:
net_price = int(net_price * (1 - (float(a.discount) / 100)))
self.count += 1
print(self.count)
if self.count == 1:
stripe.api_key = settings.API_KEY
charge_rtn_body = stripe.Charge.create( # Create charge object
amount=net_price,
currency="usd",
source="tok_visa", # obtained with Stripe.js
description= "[Stripe charge] " + a.description.upper()
)
return ClothingModel.objects.filter(id=self.kwargs['num'])
serializers.py
class ChargeSerializer(serializers.ModelSerializer):
class Meta:
model = ClothingModel
fields = ('full_price', 'discount', 'description')
I am creating a charge object for Stripe (payment method) each time the api gets called, but dependent on the clothing item id. So to handle this I use self.kwargs on get_queryset() to link to a clothing item. When I view the charges on my Stripe dashboard after a single click, multiple charges come at once (4 at a time). I have hard coded the if self.count == 1: as a work around but know that is not good practice. Is there a reason for these multiple calls in get_queryset() per single request and how can I cleanly implement? Thank you.
Objects should be created only in POST requests. get_queryset is called everytime a view is invoked (even for GET requests). So the object create should be moved inside the view function. The dynamic part of the URL is accessible from the view function as present here - https://docs.djangoproject.com/en/2.1/topics/http/urls/#example
class ChargeView(ListCreateAPIView):
def post(self, request, num):
charge = ClothingModel.objects.get(id=num)
from django.conf import settings # new
from django.views.generic.base import TemplateView
import stripe
stripe.api_key = settings.STRIPE_SECRET_KEY
class HomePageView(TemplateView):
template_name = 'stripe.html'
def get_context_data(self, **kwargs): # new
context = super().get_context_data(**kwargs)
context['key'] = settings.STRIPE_PUBLISHABLE_KEY
return context
def charge(request): # new
if request.method == 'POST':
charge = stripe.Charge.create(amount=5000,currency='ind',description='A Django charge',
source=request.POST['stripeToken'])
return render(request, 'charge.html')

pass value from one class/function to another class/function

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?

django rest framework detail_route not working in get method

I defined a viewset using ModelViewSet as follow
I tried to redefine the GET method to do something like getting something from celery . but this part of code just won't work , it acts just like a standard API and didn't do what I wrote in the get_job_detail function.
How should I correctly define the "detail_route" function.
views.py
class JobViewSet(viewsets.ModelViewSet):
queryset = job.objects.all()
serializer_class = JobSerializer
#detail_route(methods=['get'])
def get_job_detail(self, request, pk=None):
# print('these part wont proceed')
job_item = self.get_object()
if job_item.isReady or job_item.isSuccessful:
return Response(self.serializer_class(job_item).data)
celeryjob = sometask.AsyncResult(pk)
celeryjob.get()
if celeryjob.state == 'SUCCESS':
job_item.state = celeryjob.state
job_item.result = celeryjob.result
job_item.isReady = True
job_item.isSuccessful = True
job_item.save()
if celeryjob.state == 'FAILURE':
job_item.state = celeryjob.state
job_item.result = celeryjob.result
job_item.isReady = True
job_item.isSuccessful = False
job_item.save()
return Response(self.serializer_class(job_item).data)
urls.py
from django.conf.urls import url, include
from apply_api import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'job',views.JobViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
now your correct url is: /job/<pk>/get_job_detail if you want just: /job/<pk> you don need to use #detail_route just rename your method to the def retrieve(self, request, *args, **kwargs): more details retrievemodelmixin one of the part class of the modelviewset

Categories

Resources