How to conditionally pass values to django's queryset.values - python

I have a HTML form that is being submitted to a django view via get method. The goal is to print a list of users from the users table but only print the fields that have been selected in the form i.e. the user will select the columns that they want to print.
In my View, I check to see if a given column is selected then append it to a list. I then pass this list to django's queryset.values() method. However, it does not accept a list object as an argument.
class PrintUsers(TemplateView):
template_name = 'users/print-users.html'
def get(self, request, *args, **kwargs):
context = super(PrintUsers, self).get_context_data(**kwargs)
users_list = Member.objects.all()
values_list = []
if self.request.GET.get('category') == 'on':
values_list.append("category")
if self.request.GET.get('email') == 'on':
values_list.append("email")
users_list.values(values_list) # this does not work
context['users_list'] = users_list
return render(request, self.template_name, context=context)
How can I conditionally build the *fields values as the documentation does not explain this here https://docs.djangoproject.com/en/2.1/ref/models/querysets/#values ??

Related

Passing Form Data to View

I have the following view:
views.py
def PackingListView(request):
if request.method == "POST":
form = PackingListForm(request.POST)
if form.is_valid():
if 'preview' in request.POST:
request.session['data'] = form.cleaned_data
return redirect('myview')
....
I would like to take the data in form and pass it to this next view, and set the data variable equal to it. This was previously working, but once I added a foreign key into this form, the session no longer works as it is not serializable. What approach is the safest for me to take here?
views.py
class myview(View):
def get(self, request, *args, **kwargs):
data = request.session.pop('data', {})#this won't work now
pdf = render_to_pdf('packlist_preview.html', data)
return HttpResponse(pdf, content_type='application/pdf')
Also in case it is needed - here is the URL for myview
url(r'^myview/', views.myview.as_view(), name='myview'),
You should be able to serialize the data if you replace the model instance with its id.
data = form.cleaned_data
# remove object from data dict
related_object = data.pop('related_object')
# add in a reference
data['related_object_id'] = related_object.pk
# now you should be able to serialize object
request.session['data'] = data
Then in the next view, you can fetch the object from the database using its id
data = request.session.pop('data', {})
related_object_id = data.pop('related_object_id', None)
if related_object_id:
try:
data['related_object'] = RelatedObject.objects.get(pk=related_object_id)
except RelatedObject.DoesNotExist:
pass

How to export django admin changelist as csv

I want to export my changelist (fields in list_display) as csv. I used the code from https://books.agiliq.com/projects/django-admin-cookbook/en/latest/export.html But it creates csv for the model fields. But in my case, I want to export changelist as csv, not the model fields.
Also, note that most of the fields in changelist(list_display) are calculated fields in Admin.
This is my code
class ExportCsvMixin:
def export_as_csv(self, request, queryset):
meta = queryset.model._meta
field_names = [field.name for field in meta.fields]
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
writer = csv.writer(response)
writer.writerow(field_names)
for obj in queryset:
row = writer.writerow([getattr(obj, field) for field in field_names])
return response
export_as_csv.short_description = "Export Selected"
class MyAdmin(admin.ModelAdmin, ExportCsvMixin):
list_display = ('field1',
'field2'
)
list_filter = ('field2')
actions = ["export_as_csv"]
def field1(self, obj):
<return logic here>
def field2(self, obj):
<return logic here>
NOTE:
field1 and field2 are calculated fields, and not model fields.
My model is a proxy model. But I don't think it would any difference in this case.
I want the csv to contain data for field1 and field2only, as they are in my changelist.
May be the trick would be to somehow point the queryset to that of changelist. but how do that ? Or if someone can suggest some other solution or even api to achieve this goal?
I had the same issue and I managed to hack it this way.
I looked into the ModelAdmin base class and I found the function responsible for handling actions methods, it's called response_action, I looked into it and changed the queryset it return what I need.
Let say you have a query set that return 'field1' and 'field2'.
Here is how I edited the function to return a custom queryset:
def response_action(self, request, queryset):
"""
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and
None otherwise.
"""
# There can be multiple action forms on the page (at the top
# and bottom of the change list, for example). Get the action
# whose button was pushed.
try:
action_index = int(request.POST.get('index', 0))
except ValueError:
action_index = 0
# Construct the action form.
data = request.POST.copy()
data.pop(admin.helpers.ACTION_CHECKBOX_NAME, None)
data.pop("index", None)
# Use the action whose button was pushed
try:
data.update({'action': data.getlist('action')[action_index]})
except IndexError:
# If we didn't get an action from the chosen form that's invalid
# POST data, so by deleting action it'll fail the validation check
# below. So no need to do anything here
pass
action_form = self.action_form(data, auto_id=None)
action_form.fields['action'].choices = self.get_action_choices(request)
# If the form's valid we can handle the action.
if action_form.is_valid():
action = action_form.cleaned_data['action']
select_across = action_form.cleaned_data['select_across']
func = self.get_actions(request)[action][0]
# Get the list of selected PKs. If nothing's selected, we can't
# perform an action on it, so bail. Except we want to perform
# the action explicitly on all objects.
selected = request.POST.getlist(admin.helpers.ACTION_CHECKBOX_NAME)
if not selected and not select_across:
# Reminder that something needs to be selected or nothing will
# happen
msg = _("Items must be selected in order to perform "
"actions on them. No items have been changed.")
self.message_user(request, msg, messages.WARNING)
return None
if not select_across:
##### change this line with your queryset that return field one and two
queryset = 'your_queryset_with_field'
response = func(self, request, queryset)
# Actions may return an HttpResponse-like object, which will be
# used as the response from the POST. If not, we'll be a good
# little HTTP citizen and redirect back to the changelist page.
if isinstance(response, HttpResponseBase):
return response
else:
return HttpResponseRedirect(request.get_full_path())
else:
msg = _("No action selected.")
self.message_user(request, msg, messages.WARNING)
return None
Check in this function the part I command with #####
redefine that function in the class that inherits from the model admin class
The function returns a custom queryset , now you can edit your export_as_csv_function according to that.
def export_as_csv(self, request, queryset):
field_names = ["field_one", "field_two"]
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(
'working_hours')
writer = csv.writer(response)
writer.writerow(field_names)
for obj in queryset:
writer.writerow([obj.get(field) for field in field_names])
return response
That it and you can go and download your CSV with the customs field.
I hope it's not too late and this helps other folks with the same issue.

Django form field update

I have a Django form that lets users choose a tag from multiple tag options. The problem I am facing is that even when the tag list gets updated, the model form does not get the updated tag list from database. As a result, new tags do not appear in options.
Here is my code in forms.py:
class EnglishTagForm(forms.Form):
tag_choices = [(x.tagName, x.tagName.upper()) for x in ClassTag.objects.filter(
agentId=Agent.objects.get(name='English Chowdhury'))]
tag = forms.CharField(widget=forms.Select(choices=tag_choices,
attrs={'class':'form-control'}))
def __init__(self, *args, **kwargs):
super(EnglishTagForm, self).__init__(*args, **kwargs)
self.fields['tag'].choices = [(x.tagName,
x.tagName.upper()) for x in ClassTag.objects.filter(
agentId=Agent.objects.get(name='English Chowdhury'))]
This form is being instantiated in view. My question is what changes should I do so that tag_choices gets updated from database on every instantiation.
How the above form is used in views.py:
```
def complaintDetail(request, complaint_id):
complaint = Complaints.objects.filter(pk=complaint_id).first()
context = {}
if request.method == 'POST':
agent = Agent.objects.get(name="English Chowdhury")
if "SubmitTag" in request.POST:
englishForm = EnglishTagForm(request.POST)
if englishForm.is_valid:
// Complaint Delete Logic
return redirect('chatbot:modComplaints')
else:
englishForm = EnglishTagForm()
context['eForm'] = englishForm
elif "SubmitBundle" in request.POST:
newTagForm = NewTagForm(request.POST)
if newTagForm.is_valid():
// Complaint Delete Logic
complaint.delete()
return redirect('chatbot:modComplaints')
else:
newTagForm = NewTagForm()
context['newForm'] = newTagForm
else:
englishForm = EnglishTagForm()
context['eForm'] = englishForm
newTagForm = NewTagForm()
context['newForm'] = newTagForm
context['complaint'] = complaint
return render(request, 'chatbot/complaintDetail.html', context)
```
Edit: (For future reference)
I decided to modify the tag attribute and convert CharField to ModelChoiceField, which seems to fix the issue.
Updated Class:
class EnglishTagForm(forms.Form):
tag = forms.ModelChoiceField(queryset=ClassTag.objects.filter(
agentId=Agent.objects.get(name='English Chowdhury')),
empty_label=None, widget=forms.Select(
attrs={'class':'form-control'}))
Please remove the list comprehension from Line 2. So that,
tag_choices = [(x.tagName, x.tagName.upper()) for x in ClassTag.objects.filter(
agentId=Agent.objects.get(name='English Chowdhury'))]
becomes
tag_choices = []

Filtering data with __range

I'm using django_tables2 thus part of the code is dependent on this package, but this should be irrelevant to the overall problem.
forms.py
class PersonForm(forms.Form):
date_from = forms.DateField(input_formats='%d/%m/%Y')
date_to = forms.DateField(input_formats='%d/%m/%Y')
views.py
def filter(request):
table = PersonTable(Person.objects.all())
if request.method == 'POST':
form = PersonForm(request.POST)
if form.is_valid():
date_from = form.cleaned_data['date_from']
date_to = form.cleaned_data['date_to']
result_filtered = table.filter(date__range=(date_from, date_to))
RequestConfig(request, paginate={"per_page": 100}).configure(table)
return render(request, "people.html", {
"table": result_filtered })
args = {}
args.update(csrf(request))
args['form'] = PersonForm()
args['table'] = table
RequestConfig(request, paginate={"per_page": 100}).configure(table)
return render(request, 'people.html', args)
Simply, filtering is not working. I can see the entire table, but when I try to filter nothing happens. Can you see what's wrong?
I'm pretty sure you need to call .filter() on the query set rather than the table. For example:
result_filtered = PersonTable(Person.objects.filter(date__range=(date_from, date_to))
Also, on this line:
RequestConfig(request, paginate={"per_page": 100}).configure(table)
You are passing in table. You should pass in result_filtered instead.
This is one way I'd do it, assuming your Person model has a date field:
def filter(request):
if 'date_from' in request.GET and 'date_to' in request.GET:
# form data has been submitted
form = PersonForm(request.GET)
if form.is_valid():
date_from = form.cleaned_data['date_from']
date_to = form.cleaned_data['date_to']
people = Person.objects.filter(date__range=(date_from, date_to))
table = PersonTable(people)
else:
table = PersonTable(Person.objects.all())
else:
form = PersonForm()
table = PersonTable(Person.objects.all())
RequestConfig(request, paginate={"per_page": 100}).configure(table)
args = {}
args.update(csrf(request))
args['form'] = form
args['table'] = table
return render(request, 'people.html', args)
This binds the form if both its expected fields are present, and limits the queryset according to the results if valid. If not valid, it renders the bound form and the table built from the unlimited table. If form data was not submitted, it renders the table built from the unlimited table.
Your form tag's method attribute should be GET rather than POST, if using this design.
This doesn't quite follow the usual Django patterns for form handling because the form isn't actually making any changes, so you can use GET and don't need to return a redirect on success.

Filter only if the value is defined in Django

I have the following view:
def process(request):
if request.method == 'POST':
data = request.POST
results = Specs.objects.filter(screenGroup = data['screen_user'], storage = data['storage_user'], mSystem = data['system_user'] )
context = {'results' : results}
return render(request, 'process.html', context)
When the user inputs the three values it filters correctly, but when it just inputs one or two (or nothing), then it filters passing the value None. Is there any way to ignore the filter if it's not set?
Thanks!
EDIT:
The following code is working, but it's obviously a very unefficient way:
def process(request):
if request.method == 'POST':
data = request.POST
if(data['screen_user'] != None):
results = Specs.objects.filter(screenGroup = data['screen_user'])
elif (data['storage_user'] != None):
results = Specs.objects.filter(storage = data['storage_user'])
else:
results = Specs.objects.all()
#plus all the other options...
context = {'results' : results}
return render(request, 'process.html', context)
You can build the filter beforehand:
def process(request):
if request.method == 'POST':
data = request.POST
spec_filter = {}
for attribute in ['screenGroup', 'storage', 'mSystem']:
if attribute in data and data[attribute]:
spec_filter[attribute] = data[attribute]
results = Specs.objects.filter(**spec_filter)
context = {'results' : results}
return render(request, 'process.html', context)
NB: To use this verbatim you would have to change the names of the variables being passed in the request.POST to match those in the Specs model. I did this just to illustrate, but you can easily use the same principle with your variable names. In that case you'll have to be a bit more verbose.
It's called validating your form.. There are two ways of doing this:
create a django form and use myform.is_valid(). You can read about it in the docs
validate it yourself with a few 'if' statements (either on server side or with javascript before sending the ajax call)

Categories

Resources