MultiValueDictKeyError at /search/ when using post request in django - django

I am Unable to get query, which user is searching for from a POST request
Template file
<!-- search bar -->
<form action="{% url 'search' %}" method="post">{% csrf_token %}
<div class="container py-3 row">
<div class="col-md-8 offset-2">
<div class="input-group">
<input name="searchfieldText" type="text" class="form-control" placeholder="Search">
<button class="btn btn-danger" type="submit">Search</button>
</div>
</div>
</div>
</form>
urls.py
......
path('search/', views.search,name='search'),
.....
views.py
def search(request):
usr_qry= request.POST['searchfieldText']
print(usr_qry)
# and some more code which is irrelavent to paste here
.....
Complete views.py
from django.db.models.query_utils import Q
from App_wfi_Community import signals, urls
from django.shortcuts import render
from django.http import request, HttpResponseRedirect, HttpResponse
# import the models
from .models import Question, Answer, Comment, Upvote, DownVote, Upvote_record
# import paginator for pagination
from django.core.paginator import Paginator
# import forms
from .forms import Write_Answer_form, CommentForm
# import user
from django.contrib.auth.models import User
# import timezone for update function
from django.utils import timezone
# reverse for efficient url redirecting
from django.urls import reverse
from django.shortcuts import redirect
# import helper functions
from App_wfi_Community.view_helpers import *
# custom middleware
from App_wfi_Community.decorators import authentication_required, welcome_user_auth
# Create your views here.
# functions to called in views
def getPageObject(request, all_qns):
"""Returns the page object of Questions"""
paginator = Paginator(all_qns, 4, orphans=3)
pageNo = request.GET.get('page')
def getTags(RequestedQuestion):
"""Returns the Tags with comma seprated(if available)"""
# get tags from Question model
Question_tags = RequestedQuestion.Tags
# check if tag are not empty
if Question_tags != None:
Tags = Question_tags.split(',')
else:
Tags = Question_tags
# return the tags
return Tags
def get_save_form_data(RequestedQuestion, request, fom):
"""get data from form & save to DB"""
related_question = RequestedQuestion
detail = fom.cleaned_data['detail']
ansGiver = User.objects.get(pk=request.user.id)
ans = Answer(AnsGiver=ansGiver,
related_question=related_question, detail=detail)
ans.save()
# functions to process and send data to templates
#welcome_user_auth
def index(request):
# check if user is typing something
all_qns = Question.objects.all().order_by('-id')
# passing all questions to paginator with 4 question for one page
paginator = Paginator(all_qns, 6, orphans=2)
# get page no from home.html element with name= 'page'
page_number = request.GET.get('page')
# making page object
page = paginator.get_page(page_number)
# get all answer objects
all_ans = Answer.objects.all()
# get the username to display on home
username = request.user.username
# pass all the data to dictionary
data = {'all_ans': all_ans, 'all_qns': all_qns,
'page_object': page, 'username': username}
return render(request, 'home.html', data)
#authentication_required
def detail(request, questionID):
# get the Question from ID
RequestedQuestion = Question.objects.get(id=questionID)
# get the tags
Tags = getTags(RequestedQuestion)
# get the Answer
ans_A = Answer.objects.filter(related_question=RequestedQuestion)
# get the comment form
commentform = CommentForm()
# pass the info to data
data = {
'RequestedQuestion': RequestedQuestion,
'ans_A': ans_A,
'Tags': Tags,
'commentForm': commentform,
}
return render(request, 'detail.html', data)
#authentication_required
def writeAns(request, questionID):
# get the Question from ID
RequestedQuestion = Question.objects.get(id=questionID)
# check if there is a post request from template
if request.method == 'POST':
# get all the form data with post request into a variable
fom = Write_Answer_form(request.POST)
if fom.is_valid():
get_save_form_data(RequestedQuestion, request, fom)
# make a string url to pass as a arguments
url = '/detail/' + str(questionID)
return HttpResponseRedirect(url)
else:
return HttpResponse('you write the answer incorrectly')
else:
# send blank form to template
fom = Write_Answer_form()
data = {'form': fom}
return render(request, 'writeAns.html', data)
#authentication_required
def saveComment(request, ansID, questionID):
if request.method == 'POST':
fom = CommentForm(request.POST)
if fom.is_valid():
answer = Answer.objects.get(id=ansID)
commented_By = User.objects.get(pk=request.user.id)
detail = fom.cleaned_data['detail']
comment = Comment(
answer=answer, commented_By=commented_By, detail=detail)
comment.save()
# redirect the user to previous page
url = '/detail/' + str(questionID)
return HttpResponseRedirect(url)
# update the Answer
#authentication_required
def update(request, ansID):
if request.method == 'POST':
fom = Write_Answer_form(request.POST)
if fom.is_valid():
answer = Answer.objects.filter(id=ansID)
# getting data for saving
ansGiver = User.objects.get(pk=request.user.id)
related_question = answer[0].related_question
detail = '[Edited] ' + fom.cleaned_data['detail']
post_time = timezone.now()
# saving to database (for update we need to add all fields including answer id and post time)
ansObj = Answer(id=ansID, AnsGiver=ansGiver,
related_question=related_question, detail=detail, post_time=post_time)
ansObj.save()
# getting question id for redirecting the url
question = Question.objects.get(title=related_question)
url = '/detail/' + str(question.id)
return HttpResponseRedirect(url)
else:
fom = Write_Answer_form()
data = {'form': fom}
return render(request, 'update.html', data)
# delete the answer
#authentication_required
def deleteAns(request, ansID):
answerOb = Answer.objects.get(id=ansID)
question = Question.objects.get(title=answerOb.related_question)
answerOb.delete()
# url for redirection
url = '/detail/'+str(question.id)
return HttpResponseRedirect(url)
# search function
#authentication_required
def search(request):
usr_qry= request.GET['searchfieldText']
print(usr_qry)
# for use with postgre
# search_res1= Question.objects.filter(detail__icontains=usr_qry).order_by('id')
# search_res2= Question.objects.filter(title__icontains=usr_qry).order_by('id')
# final_res= search_res1.union(search_res2)
final_res= Question.objects.filter(Q(title__icontains=usr_qry) or Q(detail__icontains=usr_qry))
print('the search res are:',final_res)
pag_ob= Paginator(final_res,per_page=3)
print(f"Total no of question found with search are: {pag_ob.count}")
print(f"Total no of pages found with the question are: {pag_ob.num_pages}")
print(f"The Page range is: {pag_ob.page_range}")
print('the item in the page 1 are:',pag_ob.page(1).object_list)
return HttpResponse('chk terminal')
def downVote(request, ansID, quesID):
url= "/detail/"+str(quesID)
usr= User.objects.get(pk= request.user.id)
ansob= Answer.objects.get(id=ansID)
if chk_downvote_record(usr,ansob) == "yes":
print('this ans is already downvoted by: ',usr)
elif chk_downvote_record(usr,ansob) == "no":
save_to_downvote_record(usr,ansob)
update_downvote(ansob)
signals.delete_upVote.send(sender=None,ans=ansob,usr=usr)
return HttpResponseRedirect(url)
def upvote(request, ansID, quesID):
url = "/detail/"+str(quesID)
usr = User.objects.get(pk=request.user.id)
ans_ob = Answer.objects.get(id=ansID)
if check_upvote_record(usr, ans_ob) == "yes":
pass
elif check_upvote_record(usr, ans_ob) == "no":
save_to_upvt_rec(usr, ans_ob)
update_upvote(ans_ob)
signals.delete_downvote.send(sender=None,ans=ans_ob,usr=usr)
return HttpResponseRedirect(url)
Traceback
Environment:
Request Method: GET
Request URL: http://127.0.0.1:8000/search/?searchfieldText=window
Django Version: 3.2.2
Python Version: 3.9.5
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'App_wfi_Community',
'django.contrib.humanize',
'askQuestion',
'authentiCation']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback (most recent call last):
File "C:\Users\saurav\Documents\GitHub\WFI-Community\venv\lib\site-packages\django\utils\datastructures.py", line 76, in __getitem__
list_ = super().__getitem__(key)
During handling of the above exception ('searchfieldText'), another exception occurred:
File "C:\Users\saurav\Documents\GitHub\WFI-Community\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\saurav\Documents\GitHub\WFI-Community\venv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\saurav\Documents\GitHub\WFI-Community\App_wfi_Community\decorators.py", line 7, in wrapper_func
return func_arg(request, *args, **kwargs)
File "C:\Users\saurav\Documents\GitHub\WFI-Community\App_wfi_Community\views.py", line 178, in search
usr_qry= request.POST['searchfieldText']
File "C:\Users\saurav\Documents\GitHub\WFI-Community\venv\lib\site-packages\django\utils\datastructures.py", line 78, in __getitem__
raise MultiValueDictKeyError(key)
Exception Type: MultiValueDictKeyError at /search/
Exception Value: 'searchfieldText'
i want to get user search query or whatever user is searching for.. initially i am trying to print user search query in Terminal but it gives me:
MultiValueDictKeyError at /search/
'searchfieldText'
if i use get method then it works fine but i want to use post method & i don't know what is going wrong here.

As I see in traceback, your browser sent GET request
Request Method: GET
Request URL: http://127.0.0.1:8000/search/?searchfieldText=window
Did you reload the page after changing template?

Related

Why does Django add a dollar sign in my URL pattern? (NoReverseMatch)

I spent my entire day trying to fix this bug and I have no idea how to do it. I am trying to create a new markdown file and redirect the user to the new page after submitting the form. I tried to change the URL name, tried to change variable names but nothing works. The problem may be the fact that Django adds a dollar sign in my URL pattern but I am not sure about it because I have tried to give the URL the value of an existing page. I would appreciate a detailed explanation for this problem.
my views.py:
def entry(request, entry):
entryPage = util.get_entry(entry)
if entryPage is None:
message = messages.warning(request, 'There is not a page with this name.')
return render(request, 'encyclopedia/error.html', {
'entryTitle': entry,
'message': message
})
else:
return render(request, 'encyclopedia/entry.html',{
'entry': markdowner.convert(entryPage),
'title': entry
})
def new(request):
if request.method == 'POST':
# Get the form
form = Post(request.POST)
if form.is_valid():
# Clean the form
title = form.cleaned_data['title']
lower_title = form.cleaned_data['title'].lower()
text = form.cleaned_data['textarea']
entries = util.list_entries()
# Check for existing entry
for entry in entries:
if lower_title == entry.lower():
message = messages.warning(request, "There is already a page with this name.")
context = {
'message': message,
"form": Search(),
"post": Post()
}
return render(request, 'encyclopedia/new.html',context)
# Create and redirect to new entry
else:
util.save_entry(title,text)
return HttpResponseRedirect(reverse("entry", args=[title]))
else:
return render(request, 'encyclopedia/new.html', {
"form": Search(),
"post": Post()
})
The error I receive:
NoReverseMatch at /new
Reverse for 'entry' with arguments '('NewTitle',)' not found. 1 pattern(s) tried: ['entry$']
My urls.py:
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("wiki/<str:entry>", views.entry, name="title"),
path("new", views.new, name="new"),
path("error", views.error, name="error"),
path("entry", views.entry, name="entry")
]
Also when I check the local vars inside the Django Traceback, I don't see the title from. This may be caused because the iteration is executed just once until the error occurs.
You forgot to add a parameter for the entry to your entry URL pattern:
urlpatterns = [
# …,
path('entry/<str:entry>/', views.entry, name='entry')
]

How to post data with HttpResponseRedirect in Django?

How to post data with HttpResponseRedirect?
I have a button in Django admin change form:
<input type="submit" value="Test Peer" name="_peer-test">
and the view to get it:
def response_change(self, request, obj): ### response
if "_peer-test" in request.POST:
url = request.POST.get('obj', '/export')
return HttpResponseRedirect(url)
The first line def response_change(self, request, obj): has data
So if I type obj.name I will get the requested data.
What I want is to redirect to another view en post de information.
You can get your data from POST and send it as GET parametrs. See
def response_change(self, request, obj): ### response
if "_peer-test" in request.POST:
url = request.POST.get('obj', '/export')
if url != '/export':
# because "obj" in variable url
name = url.name
url += "?name={}".format(name)
return HttpResponseRedirect(url)
Thus you can get this value in view that need to recieve it. Example:
def reciever_view(request):
name = request.GET.get('name','')
# do some with name ...

How to add few payment methods to django-oscar

I already add cash_on_delivery payment method to my project. But i want to add one more method. How i can do it. At this moment i have code of checkout views like this:
class PaymentDetailsView(PaymentDetailsView):
template_name = 'checkout/payment-details.html'
template_name_preview = 'checkout/preview.html'
def get_context_data(self, **kwargs):
ctx = super(PaymentDetailsView, self).get_context_data(**kwargs)
ctx['signature'] = gateway.hmac_gen(ctx)
ctx['amount'] = '%s' % ctx['order_total'].incl_tax
ctx['price'] = '%s' % ctx['basket'].lines.all()[0].price_incl_tax
ctx['source'] = ctx
return ctx
# def handle_payment_details_submission(self, request):
# # Validate the submitted forms
# shipping_address = self.get_shipping_address(
# self.request.basket)
# address_form = BillingAddressForm(shipping_address, request.POST)
#
# if address_form.is_valid():
# address_fields = dict(
# (k, v) for (k, v) in address_form.instance.__dict__.items()
# if not k.startswith('_') and not k.startswith('same_as_shipping'))
# self.checkout_session.bill_to_new_address(address_fields)
# return self.render_preview(request, billing_address_form=address_form)
#
# # Forms are invalid - show them to the customer along with the
# # validation errors.
# return self.render_payment_details(
# request, billing_address_form=address_form)
def handle_payment(self, order_number, total, **kwargs):
reference = gateway.create_transaction(order_number, total)
source_type, is_created = SourceType.objects.get_or_create(
name='Cash on Delivery')
source = Source(
source_type=source_type,
currency=total.currency,
amount_allocated=total.incl_tax,
amount_debited=total.incl_tax
)
self.add_payment_source(source)
self.add_payment_event('Issued', total.incl_tax, reference=reference)
Maybe i can make with payment like with adding shipping methods?
Docdata was a very good inspiration for my implementation.
Multiple payment methods, means you will first have to list all methods somewhere (for easy enable/disable or something else) and then decide what methods are applicable to given user (depending on currency, basket size, location, etc.) All this can be handled by extending the PaymentMethodView.
settings.py
from oscar import get_core_apps as get_oscar_apps
...
INSTALLED_APPS = [
...
] + get_oscar_apps([
'checkout',
]) + [
'cashondelivery', # https://github.com/ashishnitinpatil/django-oscar-cash-on-delivery
'custom_payment', # my local app for custom payment gateway
]
OSCAR_PAYMENT_METHODS = (
('cod', _('Cash on delivery')),
('custom_payment', _('Credit / Debit card')),
)
...
checkout/forms.py
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
class PaymentMethodForm(forms.Form):
"""
Extra form for the custom payment method.
"""
payment_method = forms.ChoiceField(
label=_("Select a payment method"),
choices=settings.OSCAR_PAYMENT_METHODS,
widget=forms.RadioSelect()
)
def get_payment_method_display(payment_method):
return dict(settings.OSCAR_PAYMENT_METHODS).get(payment_method)
checkout/views.py
from django.utils import six
from django.conf import settings
from django.shortcuts import redirect
from django.views.generic import FormView
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse, reverse_lazy
from . import forms
from custom_payment import facade
from cashondelivery import gateway as cod_gateway
from oscar.apps.checkout import exceptions
from oscar.core.loading import get_model, get_class
Source = get_model("payment", "Source")
SourceType = get_model("payment", "SourceType")
RedirectRequired = get_class("payment.exceptions", "RedirectRequired")
UnableToPlaceOrder = get_class('order.exceptions', 'UnableToPlaceOrder')
OscarPaymentMethodView = get_class("checkout.views", "PaymentMethodView")
OscarPaymentDetailsView = get_class("checkout.views", "PaymentDetailsView")
OscarShippingMethodView = get_class("checkout.views", "ShippingMethodView")
# Sample pre-condition
class CheckCountryPreCondition(object):
"""DRY class for check country in session pre_condition"""
def get_pre_conditions(self, request):
if 'check_country_in_session' not in self.pre_conditions:
return self.pre_conditions + ['check_country_in_session']
return super().get_pre_conditions(request)
def check_country_in_session(self, request):
if request.session.get('country', None) is None:
raise exceptions.FailedPreCondition(
url=reverse('checkout:shipping-address'),
)
# Inspired by https://github.com/django-oscar/django-oscar-docdata/blob/master/sandbox/apps/checkout/views.py
class PaymentMethodView(CheckCountryPreCondition, OscarPaymentMethodView, FormView):
"""
View for a user to choose which payment method(s) they want to use.
This would include setting allocations if payment is to be split
between multiple sources. It's not the place for entering sensitive details
like bankcard numbers though - that belongs on the payment details view.
"""
template_name = "checkout/payment_method.html"
step = 'payment-method'
form_class = forms.PaymentMethodForm
success_url = reverse_lazy('checkout:payment-details')
pre_conditions = [
'check_basket_is_not_empty',
'check_basket_is_valid',
'check_user_email_is_captured',
'check_shipping_data_is_captured',
'check_payment_data_is_captured',
]
skip_conditions = ['skip_unless_payment_is_required']
def get(self, request, *args, **kwargs):
# if only single payment method, store that
# and then follow default (redirect to preview)
# else show payment method choice form
if len(settings.OSCAR_PAYMENT_METHODS) == 1:
self.checkout_session.pay_by(settings.OSCAR_PAYMENT_METHODS[0][0])
return redirect(self.get_success_url())
else:
return FormView.get(self, request, *args, **kwargs)
def get_success_url(self, *args, **kwargs):
# Redirect to the correct payments page as per the method (different methods may have different views &/or additional views)
return reverse_lazy('checkout:preview')
def get_initial(self):
return {
'payment_method': self.checkout_session.payment_method(),
}
def form_valid(self, form):
# Store payment method in the CheckoutSessionMixin.checkout_session (a CheckoutSessionData object)
self.checkout_session.pay_by(form.cleaned_data['payment_method'])
return super().form_valid(form)
class PaymentDetailsView(CheckCountryPreCondition, OscarPaymentDetailsView):
def handle_payment(self, order_number, order_total, **kwargs):
method = self.checkout_session.payment_method()
if method == 'cod':
return self.handle_cod_payment(order_number, order_total, **kwargs)
elif method == 'custom_payment':
return self.handle_custom_payment_payment(order_number, order_total, **kwargs)
else:
raise PaymentError(_('Bad payment method in handle_payment!'))
def handle_cod_payment(self, order_number, total, **kwargs):
reference = cod_gateway.create_transaction(order_number, total)
source_type, is_created = SourceType.objects.get_or_create(
name='Cash on delivery')
source = Source(
source_type=source_type,
currency=total.currency,
amount_allocated=total.incl_tax,
amount_debited=total.incl_tax
)
self.add_payment_source(source)
self.add_payment_event('awaiting-delivery', total.incl_tax, reference=reference)
def handle_custom_payment_payment(self, order_number, total, **kwargs):
submission = self.build_submission(order_number=order_number, **kwargs)
# Save required payment gateway data
# also validates that we have all we need
custom_payment_payment_data = facade.get_gateway_url_and_parameters(
submission, self.request.build_absolute_uri, live=settings.custom_payment['LIVE'])
# Any raised exceptions are handled by the PaymentDetail.submit() code.
custom_payment_order = facade.create_order(
order_number=order_number,
amount=total,
payment_data=custom_payment_payment_data
)
# record payment data to session to double verify things on success / failure
self.set_custom_payment_payment_data(custom_payment_payment_data, custom_payment_order)
source = Source(
source_type=facade.get_source_type(),
currency=total.currency,
amount_allocated=total.incl_tax, # amount_* field depends on type of transaction.
reference=custom_payment_order.id
)
self.add_payment_source(source)
# Also record payment event.
# This will be visible in the Dashboard
self.add_payment_event('pre-auth', total.incl_tax, reference=custom_payment_order.id)
# Regardless of whether the order is paid, write it in the database before redirecting.
# Oscar actually skips this when redirecting the user to the payment provider.
self._save_order(order_number, submission)
# Redirect the user to the payment provider's gateway.
raise RedirectRequired(facade.get_payment_url())
def _save_order(self, order_number, submission):
# Finalize the order that PaymentDetailsView.submit() started
# If all is ok with payment, try and place order
logger.info("Order #%s: payment started, placing order", order_number)
try:
# Call OrderPlacementMixin.handle_order_placement()
return self.handle_order_placement(
order_number, submission['user'], submission['basket'],
submission['shipping_address'], submission['shipping_method'],
submission['shipping_charge'], submission['billing_address'],
submission['order_total'], **(submission['order_kwargs'])
)
except UnableToPlaceOrder as e:
# It's possible that something will go wrong while trying to
# actually place an order. Not a good situation to be in as a
# payment transaction may already have taken place, but needs
# to be handled gracefully.
logger.error("Order #%s: unable to place order - %s", order_number, e, exc_info=True)
msg = six.text_type(e)
self.restore_frozen_basket()
return self.render_to_response(self.get_context_data(error=msg))
# Can't update CheckoutSessionMixin nicely without causing trouble,
# hence dumping the overridden / new methods from that class here
def check_payment_data_is_captured(self, request):
# We don't collect payment data by default so we don't have anything to
# validate here. If your shop requires forms to be submitted on the
# payment details page, then override this method to check that the
# relevant data is available. Often just enforcing that the preview
# view is only accessible from a POST request is sufficient.
if not self.checkout_session.payment_method():
raise FailedPreCondition(
url=reverse('checkout:payment-method'),
message=_("Please select a payment method for your order!")
)
def set_custom_payment_payment_data(self, payment_data, custom_payment_order):
self.request.session['custom_payment'] = payment_data
self.request.session['custom_payment_order_id'] = custom_payment_order.id
self.request.session['ongoing_online_payment'] = True
self.request.session.pop('custom_payment_error', None)
def get_context_data(self, **kwargs):
ctx = super(PaymentDetailsView, self).get_context_data(**kwargs)
ctx.update({'payment_method': self.checkout_session.payment_method()})
return ctx
def send_confirmation_message(self, order, *args, **kwargs):
# In case of custom_payment, delay sending the order confirmation till payment success!
if not self.checkout_session.payment_method() == 'custom_payment':
super(PaymentDetailsView, self).send_confirmation_message(order, *args, **kwargs)
You may ignore the details for custom_payment since that is something specific for my project / payment gateway, you would likely have different requirements for that. I have still left a chunk of the things in it so that you may think of the things accordingly.
PS - Please note I'm using Python3.6, so a few things (like super) might not work as mentioned here, in previous Python versions (specially 2.7)
Update
My templates/checkout/payment_method.html:
{% extends "checkout/checkout.html" %}
{% load i18n %}
{% block checkout_title %}{{ form.payment_method.label }}{% endblock %}
{% block checkout_nav %}
{% include 'checkout/nav.html' with step=3 %}
{% endblock %}
{% block content %}
{% if error %}
<div class="alert alert-error">
{{ error }}
</div>
{% endif %}
<form action="" method="post">
{% csrf_token %}
{% if form.payment_method.errors %}{{ form.payment_method.errors }}{% endif %}
<div class="radio">
<label for="id_payment_method_0">
<input id="id_payment_method_0" name="payment_method" type="radio" value="cod" required="">{% trans "Cash on delivery" %}
</label>
</div>
<div class="radio">
<label for="id_payment_method_1">
<input id="id_payment_method_1" name="payment_method" type="radio" value="custom_payment" required="">{% trans "Credit / Debit Card" %}
</label>
</div>
<p><button type="submit" class="btn btn-large btn-primary">{% trans "Continue to confirmation" %}</button>
</form>
{% endblock %}

How to export ONLY selected records in django admin?

I am using the https://github.com/bendavis78/django-admin-csv to export records to csv. But it will download all records even though I select only a few.
from admin_csv import CSVMixin
class MyModelAdmin(CSVMixin, admin.ModelAdmin):
list_display = ['foo', 'bar', 'baz']
csv_fields = list_display + ['qux']
Here is the admin.py file
from functools import update_wrapper
from django.contrib.admin.utils import label_for_field
class CSVMixin(object):
"""
Adds a CSV export action to an admin view.
"""
change_list_template = "admin/change_list_csv.html"
# This is the maximum number of records that will be written.
# Exporting massive numbers of records should be done asynchronously.
csv_record_limit = None
csv_fields = []
csv_headers = {}
def get_csv_fields(self, request):
return self.csv_fields or self.list_display
def get_urls(self):
from django.conf.urls import url
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
opts = self.model._meta
urlname = '{0.app_label}_{0.model_name}_csvdownload'.format(opts)
urlpatterns = [
url('^csv/$', wrap(self.csv_export), name=urlname)
]
return urlpatterns + super(CSVMixin, self).get_urls()
def get_csv_filename(self, request):
return unicode(self.model._meta.verbose_name_plural)
def changelist_view(self, request, extra_context=None):
context = {
'querystring': request.GET.urlencode()
}
context.update(extra_context or {})
return super(CSVMixin, self).changelist_view(request, context)
def csv_header_for_field(self, field_name):
if self.headers.get(field_name):
return self.headers[field_name]
return label_for_field(field_name, self.model, self)
def csv_export(self, request, *args, **kwargs):
import csv
from django.http import HttpResponse
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = (
'attachment; filename={0}.csv'.format(
self.get_csv_filename(request)))
fields = list(self.get_csv_fields(request))
writer = csv.DictWriter(response, fields)
# Write header row.
headers = dict((f, self.csv_header_for_field(f)) for f in fields)
writer.writerow(headers)
# Get the queryset using the changelist
cl_response = self.changelist_view(request)
cl = cl_response.context_data.get('cl')
queryset = cl.get_queryset(request)
# Write records.
if self.csv_record_limit:
queryset = queryset[:self.csv_record_limit]
for r in queryset:
data = {}
for name in fields:
if hasattr(r, name):
data[name] = getattr(r, name)
elif hasattr(self, name):
data[name] = getattr(self, name)(r)
else:
raise ValueError('Unknown field: {}'.format(name))
if callable(data[name]):
data[name] = data[name]()
writer.writerow(data)
return response
csv_export.short_description = \
'Exported selected %(verbose_name_plural)s as CSV'
Here is the change_list_csv.html.
{% extends "admin/change_list.html" %}
{% load admin_urls %}
{% block object-tools-items %}
{{ block.super }}
<li>
{% url cl.opts|admin_urlname:'csvdownload' as download_url %}
Download CSV
</li>
{% endblock %}
The file looks simple but can't figure out what to change to only export the selected rows.
I think the problem is your "opts = self.model._meta". When I export to csv using a custom admin action, I use this: opts = queryset.model._meta, and the admin queryset does the limiting for me. The structure of your code is very different from mine so I don't know how you need to get the equivalent to the "queryset", but I think that is what is missing or not functioning correctly.

Tango With Django - Fun With Forms URLconf Error

I am currently doing the latest tango with django course and have just about finished '8. Fun with Forms', I however cannot solve an error I am getting. The course tells us to make an add_page form and supplies us with the view and the form, all we really have to do is create the URL, I have done this however I cannot get it to recognize the URL for the add_page view.
Sorry I cannot post pictures as I do not have high enough reputation so I have given links.
This is the error I get
http://i.stack.imgur.com/UrFxv.png
Here is my code
URLS -
from django.conf.urls import patterns, url
from rango import views
urlpatterns = patterns('',
url(r'^$', views.index, name='index'),
url(r'^about/', views.about, name="about"),
url(r'^add_category/$', views.add_category, name='add_category'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/add_page/$', views.add_page, name='add_page'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/$', views.category, name='category'),
)
VIEWS -
from django.shortcuts import render
from django.http import HttpResponse
from rango.models import Category, Page
from rango.forms import CategoryForm, PageForm
def index(request):
# Query the database for a list of ALL categories currently stored.
# Order the categories by no. likes in descending order.
# Retrieve the top 5 only - or all if less than 5.
# Place the list in our context_dict dictionary which will be passed to the template engine.
category_list = Category.objects.order_by('-likes')[:5]
page_list = Page.objects.order_by('-views')[:5]
context_dict = {'categories': category_list, 'pages': page_list}
# Render the response and send it back!
return render(request, 'rango/index.html', context_dict)
def about(request):
message = "Rango says here is the about page. <a href='/rango/'>Index</a> "
return HttpResponse(message)
def category(request, category_name_slug):
# Create a context dictionary which we can pass to the template rendering engine.
context_dict = {'category_name_slug': category_name_slug}
try:
# Can we find a category name slug with the given name?
# If we can't, the .get() method raises a DoesNotExist exception.
# So the .get() method returns one model instance or raises an exception.
category = Category.objects.get(slug=category_name_slug)
context_dict['category_name'] = category.name
# Retrieve all of the associated pages.
# Note that filter returns >= 1 model instance.
pages = Page.objects.filter(category=category)
# Adds our results list to the template context under name pages.
context_dict['pages'] = pages
# We also add the category object from the database to the context dictionary.
# We'll use this in the template to verify that the category exists.
context_dict['category'] = category
except Category.DoesNotExist:
# We get here if we didn't find the specified category.
# Don't do anything - the template displays the "no category" message for us.
pass
# Go render the response and return it to the client.
return render(request, 'rango/category.html', context_dict)
def add_category(request):
# A HTTP POST?
if request.method == 'POST':
form = CategoryForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# Save the new category to the database.
form.save(commit=True)
# Now call the index() view.
# The user will be shown the homepage.
return index(request)
else:
# The supplied form contained errors - just print them to the terminal.
print form.errors
else:
# If the request was not a POST, display the form to enter details.
form = CategoryForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render(request, 'rango/add_category.html', {'form': form})
def add_page(request, category_name_slug):
try:
cat = Category.objects.get(slug=category_name_slug)
except Category.DoesNotExist:
cat = None
if request.method == 'POST':
form = PageForm(request.POST)
if form.is_valid():
if cat:
page = form.save(commit=False)
page.category = cat
page.views = 0
page.save()
# probably better to use a redirect here.
return category(request, category_name_slug)
else:
print form.errors
else:
form = PageForm()
# made the change here
context_dict = {'form': form, 'category': cat, 'category_name_slug': category_name_slug}
return render(request, 'rango/add_page.html', context_dict)
FORMS -
from django import forms
from rango.models import Page, Category
class CategoryForm(forms.ModelForm):
name = forms.CharField(max_length=128, help_text="Please enter the category name.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
slug = forms.CharField(widget=forms.HiddenInput(), required=False)
# An inline class to provide additional information on the form.
class Meta:
# Provide an association between the ModelForm and a model
model = Category
fields = ('name',)
class PageForm(forms.ModelForm):
title = forms.CharField(max_length=128, help_text="Please enter the title of the page.")
url = forms.URLField(max_length=200, help_text="Please enter the URL of the page.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
class Meta:
# Provide an association between the ModelForm and a model
model = Page
# What fields do we want to include in our form?
# This way we don't need every field in the model present.
# Some fields may allow NULL values, so we may not want to include them...
# Here, we are hiding the foreign key.
# we can either exclude the category field from the form,
exclude = ('category',)
#or specify the fields to include (i.e. not include the category field)
#fields = ('title', 'url', 'views')
def clean(self):
cleaned_data = self.cleaned_data
url = cleaned_data.get('url')
# If url is not empty and doesn't start with 'http://', prepend 'http://'.
if url and not url.startswith('http://'):
url = 'http://' + url
cleaned_data['url'] = url
return cleaned_data
I think that is all the code necessary however just let me know if there are another files you would like to see and I'll put them up. Any help appreciated, thanks
A simple mistake, in my browser I was doing this 'http://127.0.0.1:8000/rango/category/other-frameworks/add_page.html', however my URL Tuple was not looking for a '.html' as the end so I removed it so it looked like this 'http://127.0.0.1:8000/rango/category/other-frameworks/add_page' and that solved my problem. Thanks to sgmart.