Django csv export - python

I have the following csv export function to export model information in csv file. But I am trying to figure out how to show outputs from functions in the model. So the csv export function shows all the fields in the model (fields in the table) but not outputs from functions in the model..
So if I have the following:
def avg_tag(self, obj):
bids = Bid.objects.active(user=obj.user.id)
return bids.aggregate(average_price=Avg('list_price'))['average_price']
in the model
It does not get the output from that function.
Here is the csv export function action:
def export_select_fields_csv_action(description="Export selected objects",
fields=None, exclude=None, header=True):
def export_as_csv(modeladmin, request, queryset):
"""
Generic csv export admin action.
based on http://djangosnippets.org/snippets/1697/
"""
opts = modeladmin.model._meta
field_names = [field.name for field in opts.fields]
labels = []
if exclude:
field_names = [v for v in field_names if v not in exclude]
elif fields:
field_names = [k for k, v in fields if k in field_names]
labels = [v for k, v in fields if k in field_names]
response = HttpResponse(mimetype='text/csv')
response['Content-Disposition'] = ('attachment; filename=%s.csv'
% unicode(opts).replace('.', '_'))
writer = csv.writer(response)
if header:
if labels:
writer.writerow(labels)
else:
writer.writerow(field_names)
for obj in queryset:
writer.writerow([unicode(getattr(obj, field)).encode('utf-8')
for field in field_names])
return response
export_as_csv.short_description = description
return export_as_csv
How can I change the function above so that the outputs from functions in the model are outputted in the csv? Thanks!

I guess edit the bit where you loop through the queryset and just add it on the end -
for obj in queryset:
writer.writerow([unicode(getattr(obj, field)).encode('utf-8')
for field in field_names] + "," + obj.avg_tag())
You can do something similar to add the heading you want -
if header:
if labels:
writer.writerow(labels + "," + "Average Tag")
else:
writer.writerow(field_names + "," + "Average Tag"))
UPDATE
It's going to be a bit difficult getting fields and methods because you'll find that there are lots of methods on your model which you didn't define (and you probably don't want in your csv). There's no easy way of differentiating which you want and which you don't.
Have a play with the following to see what's in there -
import inspect
obj = modeladmin.model()
methods = [a for a in dir(modeladmin.model) if inspect.ismethod(getattr(obj, a))]
for method in methods:
print str(method)
I certainly can't see an elegant way of doing this (or in fact anyway) - I think you're going to have to actually specify each method.

Related

How to generate a list of request.POST in Django

I have the following code in views.py:
def valores(request):
global peso_unitario, preco_unitario
peso_unitario=[]
preco_unitario=[]
N=a
print('N='+str(N))
for i in range(N):
peso_u = request.POST['peso_u']
preco_u = request.POST['preco_u']
if peso_u.isdigit() and preco_u.isdigit():
c = int(peso_u)
d = int(preco_u)
peso_unitario.append(c)
preco_unitario.append(d)
print(a)
if i==N-1:
return render(request, 'pacote.html', {'peso_unitario': peso_unitario, 'preco_unitario': preco_unitario})
else:
res = 'Apenas numero.'
return render(request, 'pacote.html', {'res': res})
One step before, where we filled a text field with a number N. Now, I'd like to generate N text fields to be filled by the user, but I don't know how to do this.
You should use a formset to generate N forms dynamically. You can refer to the documentation to see how to implement one.
Don't use globals. You need to store the previously retrieved number N somewhere. Maybe in the user's session. Maybe pass it to this view as a querystring, and have a default value or redirect back to the form which should have obtained it if its missing.
Anyway, having obtained that N, you can build the form with N similar forms dynamically. (As mentioned by others, another way is to use a formset).
class Baseform( forms.Form):
...
# everything apart from the variable fields
fields = {}
for n in range(N):
fields[ f'form_{n}'] = forms.Charfield( # or whatever
label = f'form{n}', ... # other form args
)
My_Dynamic_Form = type(
'My_Dynamic_Form', (BaseForm, ), fields
)
Instantiate, check as usual, process the variable fields:
form = My_Dynamic_Form( ...)
if form.is_valid():
# your variable data will be in form.cleaned_data['form_0'] upwards.
# maybe
for n in range(1000):
key = f'form_{n}'
if not (key in form.cleaned_data):
break
val = form.cleaned_data.get(key)
# do whatever with val
you can iterate all posted data using request.POST.items() example:
for key, value in request.POST.items():
print(f"{key}: {value}")
Output:
name: momo
lastname: titi

PdfFileReader.getFields() returns {} | django

I'm trying to read a pdf form with django. The point is that in another view of my views.py I've succeed into do it by using PyPDF2 and its PdfFileReader.getFields() method.
Now the problem is that the reading is not working properly: I've checked with adobe acrobat and the file still is a form with actually fields, so I don't really have any idea of what could be the problem.
I'm attaching here the relevant portion of the code:
if request.method == "POST":
form = Form(request.POST, request.FILES) # the form refer to a model called 'New Request'
if form.is_valid():
form.save()
File = request.FILES['File'].name
full_filename = os.path.join(BASE_DIR, 'media/media', File)
f = PdfFileReader(full_filename)
fields = f.getFields()
fdfinfo = dict((k, v.get('/V', '')) for k, v in fields.items())
k = creare_from_pdf2(request, fdfinfo, pk) # this is a custom function
nr = NewRequest.objects.all() #I'm deleting the object uploaded because it won't be useful anymore
nr.delete()
os.remove(full_filename)
If I display print(fdfinfo) it actually shows {}. This of course is leading to error when fdfinfo passes into the 'create_from_pdf_2' function. I don't really know what the problem could be, also because in another view I made exactly the same and it works:
if request.method=='POST':
form = Form(request.POST, request.FILES)
if form.is_valid():
form.save()
uploaded_filename = request.FILES['File'].name
full_filename = os.path.join(BASE_DIR, 'media/media', uploaded_filename)
f = PdfFileReader(full_filename)
fields = f.getFields()
fdfinfo = dict((k, v.get('/V', '')) for k, v in fields.items())
k=create_from_pdf1(request, fdfinfo)
if k==1:
return HttpResponse('<html><body>Something went wrong</html></body>')
nr = NewRequest.objects.all()
nr.delete()
os.remove(full_filename)
Maybe is there a way to display the errors of PdfFileReader?
UPDATING
The new file that I'm trying to reading is firstly modified in the sense that some (BUT NOT ALL!) fields are filled with PdfFileWriter, and the one filled are set then to only readable. Could this operation have influenced the performances of PdfFileReader? I'm attaching the correspondent view
att = MAIN.objects.get(id=pk)
file_path = os.path.join(BASE_DIR, 'nuova_form.pdf')
input_stream = open(file_path, "rb")
pdf_reader = PdfFileReader(input_stream, strict = False)
if "/AcroForm" in pdf_reader.trailer["/Root"]:
pdf_reader.trailer["/Root"]["/AcroForm"].update(
{NameObject("/NeedAppearances"): BooleanObject(True)})
pdf_writer = PdfFileWriter()
set_need_appearances_writer(pdf_writer)
if "/AcroForm" in pdf_writer._root_object:
# Acro form is form field, set needs appearances to fix printing issues
pdf_writer._root_object["/AcroForm"].update(
{NameObject("/NeedAppearances"): BooleanObject(True)})
data_dict1 = { # my text fields
}
data_dict2 = { # my booleancheckbox fields }
for i in range(0,6): #The pdf file has 6 pages
pdf_writer.addPage(pdf_reader.getPage(i))
page = pdf_writer.getPage(i)
# update form fields
pdf_writer.updatePageFormFieldValues(page, data_dict1)
for j in range(0, len(page['/Annots'])):
writer_annot = page['/Annots'][j].getObject()
for field in data_dict1:
if writer_annot.get('/T') == field:
writer_annot.update({
NameObject("/Ff"): NumberObject(1) # make ReadOnly
})
# update checkbox fields
updateCheckboxValues(page, data_dict2)
output_stream = BytesIO()
pdf_writer.write(output_stream)
return output_stream
def updateCheckboxValues(page, fields):
for j in range(0, len(page['/Annots'])):
writer_annot = page['/Annots'][j].getObject()
for field in fields:
if writer_annot.get('/T') == field:
writer_annot.update({
NameObject("/V"): NameObject(fields[field]),
NameObject("/AS"): NameObject(fields[field])
})
I got similar results when trying to do a straightforward read of a PDF form using Python and PyPDF2. The PDF form had been created using Libre Writer and was a single page with about 50 text fields on it. When I ran the getFields() method on the reader object I was getting the same issue -- it was returning an empty dict object.
I thought there might be a limitation on the number of fields and tried removing some for testing, but got the same result. Then when looking at it I noticed the fieldnames were all pretty long: txtLabMemberFirstName01, txtLabMemberLastName01, txtPrincipalInvestigatorFirstName, etc.
I shortened all the fields' names (e.g., "txtLMFN01") and PyPDF2 started working again as expected.

Django View Return Queryset with extra information

I have a normal Django view that returns the API for a query set. It takes query params from the URL and filters the database based on the parameters. It also outputs a maximum length of 3 "Part" objects.
I would like to add something so that it returns information on whether the queryset is clipped by the maximum length of 3. The idea is that since the inputs the query parameters, if the parameters are too vague, then there will be too much data being queried from the database. So it is clipped but then the user needs to know that it was clipped.
The current code looks like this
class PartList(generics.ListAPIView):
serializer_class = PartSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Part.objects.all()
querydict = self.request.query_params
for (k, value) in querydict.items():
search_type = 'contains'
filter = k + '__' + search_type
queryset = queryset.filter(**{filter: value})
query_max_limit = 3
return queryset[:min(len(queryset), query_max_limit)]
You can try to fetch four elements, and in case it returns four, you display the first three, and specify that the data is clipped, like:
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Part.objects.all()
querydict = self.request.query_params
for (k, value) in querydict.items():
search_type = 'contains'
filter = k + '__' + search_type
queryset = queryset.filter(**{filter: value})
query_max_limit = 3
qs = queryset[:query_max_limit+1]
self.clipped = clipped = len(qs) > query_max_limit
if clipped:
return list(qs)[:query_max_limit]
else:
return qs
So here the get_queryset will return a collection (not per se a QuerySet), containing at most three elements, and it will set an attribute self.clipped that specifies if the data was clipped.
Or a more elegant approach would be to first count, and then slice:
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Part.objects.all()
querydict = self.request.query_params
for (k, value) in querydict.items():
search_type = 'contains'
filter = k + '__' + search_type
queryset = queryset.filter(**{filter: value})
query_max_limit = 3
qs = queryset[:query_max_limit+1]
self.clipped = clipped = qs.count() > query_max_limit
if clipped:
return queryset[:query_max_limit]
else:
return qs
It might be better to move this "clipping" logic to a dedicated function, and return if it is clipped, instead of setting an attribute.
It's perfectly fine to pass metadata along with your results, like so:
{
"is_clipped": true,
"results": [
…
]
}
Willem's answer is a good way to set is_clipped.
But I think you are interested in pagination, which is a standard way to communicate to clients that the results are clipped. It's possible combine your queryset filering with pagination. By the way, I suggest you use django-filter instead of rolling your own filtering.

How do I construct an AND query on the same field in the URL of TastyPie?

I want to filter results in the tastypie to get results that conform to both of two filters on the same field.
So if I have a simple model like this...
class Item(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
With a ModelResource...
class ItemResource(ModelResource):
...
class Meta():
queryset = Item.objects.all()
resource_name = 'item'
filtering = {'name': ALL, 'description': ALL}
I can easily construct 'AND' queries in the url of tastypie:
/api/v1/item/?name__contains=hello&description__contains=foo
But if I want to construct an AND operator on the same field, it only takes the second argument and ignores the first. That is,
/api/v1/item/?name__contains=hello&name__contains=world
returns resources whose name field contains 'world' but not those whose name field contains BOTH 'hello' and 'world'.
I understand how to do this directly in django:
Item.objects.filter(name__contains='hello').filter(name__contains='world')
But how do I construct this kind of a query in the URL of the tastypie?
I'm using the below. It will give you support for name__contains=hello,world. And you could also do negations name__contains!=foo.
def build_filters(self, filters=None):
"""
Adds support for negation filtering
"""
if not filters:
return filters
applicable_filters = {}
self.filters = filters
# Normal filtering
filter_params = dict([(x, filters[x]) for x in filter(lambda x: not x.endswith('!'), filters)])
applicable_filters['filter'] = super(MainBaseResource, self).build_filters(filter_params)
# Exclude filtering
exclude_params = dict([(x[:-1], filters[x]) for x in filter(lambda x: x.endswith('!'), filters)])
applicable_filters['exclude'] = super(MainBaseResource, self).build_filters(exclude_params)
return applicable_filters
def apply_filters(self, request, applicable_filters):
"""
Adds support for:
1. negation filtering: value_date__year!=2013
2. multiple filtering value_date__year=2013,2012
"""
from django.db.models import Q
import operator
from types import *
objects = self.get_object_list(request)
f = applicable_filters.get('filter')
if f:
# Q Filters for multiple values (1,2,3 etc)
q_filters = []
for key, val in f.iteritems():
string = str(val)
if ',' in string:
for excl_filter in string.split(','):
q_filters.append((key, excl_filter))
q_list = [Q(x) for x in q_filters]
for x in q_filters:
try:
del f[x[0]]
except:
pass
if q_list:
objects = objects.filter(reduce(operator.or_, q_list), **f)
else:
objects = objects.filter(**f)
e = applicable_filters.get('exclude')
if e:
objects = objects.exclude(**e)
return objects

How would you inherit from and override the django model classes to create a listOfStringsField?

I want to create a new type of field for django models that is basically a ListOfStrings. So in your model code you would have the following:
models.py:
from django.db import models
class ListOfStringsField(???):
???
class myDjangoModelClass():
myName = models.CharField(max_length=64)
myFriends = ListOfStringsField() #
other.py:
myclass = myDjangoModelClass()
myclass.myName = "bob"
myclass.myFriends = ["me", "myself", "and I"]
myclass.save()
id = myclass.id
loadedmyclass = myDjangoModelClass.objects.filter(id__exact=id)
myFriendsList = loadedclass.myFriends
# myFriendsList is a list and should equal ["me", "myself", "and I"]
How would you go about writing this field type, with the following stipulations?
We don't want to do create a field which just crams all the strings together and separates them with a token in one field like this. It is a good solution in some cases, but we want to keep the string data normalized so tools other than django can query the data.
The field should automatically create any secondary tables needed to store the string data.
The secondary table should ideally have only one copy of each unique string. This is optional, but would be nice to have.
Looking in the Django code it looks like I would want to do something similar to what ForeignKey is doing, but the documentation is sparse.
This leads to the following questions:
Can this be done?
Has it been done (and if so where)?
Is there any documentation on Django about how to extend and override their model classes, specifically their relationship classes? I have not seen a lot of documentation on that aspect of their code, but there is this.
This is comes from this question.
There's some very good documentation on creating custom fields here.
However, I think you're overthinking this. It sounds like you actually just want a standard foreign key, but with the additional ability to retrieve all the elements as a single list. So the easiest thing would be to just use a ForeignKey, and define a get_myfield_as_list method on the model:
class Friends(model.Model):
name = models.CharField(max_length=100)
my_items = models.ForeignKey(MyModel)
class MyModel(models.Model):
...
def get_my_friends_as_list(self):
return ', '.join(self.friends_set.values_list('name', flat=True))
Now calling get_my_friends_as_list() on an instance of MyModel will return you a list of strings, as required.
What you have described sounds to me really similar to the tags.
So, why not using django tagging?
It works like a charm, you can install it independently from your application and its API is quite easy to use.
I also think you're going about this the wrong way. Trying to make a Django field create an ancillary database table is almost certainly the wrong approach. It would be very difficult to do, and would likely confuse third party developers if you are trying to make your solution generally useful.
If you're trying to store a denormalized blob of data in a single column, I'd take an approach similar to the one you linked to, serializing the Python data structure and storing it in a TextField. If you want tools other than Django to be able to operate on the data then you can serialize to JSON (or some other format that has wide language support):
from django.db import models
from django.utils import simplejson
class JSONDataField(models.TextField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if value is None:
return None
if not isinstance(value, basestring):
return value
return simplejson.loads(value)
def get_db_prep_save(self, value):
if value is None:
return None
return simplejson.dumps(value)
If you just want a django Manager-like descriptor that lets you operate on a list of strings associated with a model then you can manually create a join table and use a descriptor to manage the relationship. It's not exactly what you need, but this code should get you started.
Thanks for all those that answered. Even if I didn't use your answer directly the examples and links got me going in the right direction.
I am not sure if this is production ready, but it appears to be working in all my tests so far.
class ListValueDescriptor(object):
def __init__(self, lvd_parent, lvd_model_name, lvd_value_type, lvd_unique, **kwargs):
"""
This descriptor object acts like a django field, but it will accept
a list of values, instead a single value.
For example:
# define our model
class Person(models.Model):
name = models.CharField(max_length=120)
friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)
# Later in the code we can do this
p = Person("John")
p.save() # we have to have an id
p.friends = ["Jerry", "Jimmy", "Jamail"]
...
p = Person.objects.get(name="John")
friends = p.friends
# and now friends is a list.
lvd_parent - The name of our parent class
lvd_model_name - The name of our new model
lvd_value_type - The value type of the value in our new model
This has to be the name of one of the valid django
model field types such as 'CharField', 'FloatField',
or a valid custom field name.
lvd_unique - Set this to true if you want the values in the list to
be unique in the table they are stored in. For
example if you are storing a list of strings and
the strings are always "foo", "bar", and "baz", your
data table would only have those three strings listed in
it in the database.
kwargs - These are passed to the value field.
"""
self.related_set_name = lvd_model_name.lower() + "_set"
self.model_name = lvd_model_name
self.parent = lvd_parent
self.unique = lvd_unique
# only set this to true if they have not already set it.
# this helps speed up the searchs when unique is true.
kwargs['db_index'] = kwargs.get('db_index', True)
filter = ["lvd_parent", "lvd_model_name", "lvd_value_type", "lvd_unique"]
evalStr = """class %s (models.Model):\n""" % (self.model_name)
evalStr += """ value = models.%s(""" % (lvd_value_type)
evalStr += self._params_from_kwargs(filter, **kwargs)
evalStr += ")\n"
if self.unique:
evalStr += """ parent = models.ManyToManyField('%s')\n""" % (self.parent)
else:
evalStr += """ parent = models.ForeignKey('%s')\n""" % (self.parent)
evalStr += "\n"
evalStr += """self.innerClass = %s\n""" % (self.model_name)
print evalStr
exec (evalStr) # build the inner class
def __get__(self, instance, owner):
value_set = instance.__getattribute__(self.related_set_name)
l = []
for x in value_set.all():
l.append(x.value)
return l
def __set__(self, instance, values):
value_set = instance.__getattribute__(self.related_set_name)
for x in values:
value_set.add(self._get_or_create_value(x))
def __delete__(self, instance):
pass # I should probably try and do something here.
def _get_or_create_value(self, x):
if self.unique:
# Try and find an existing value
try:
return self.innerClass.objects.get(value=x)
except django.core.exceptions.ObjectDoesNotExist:
pass
v = self.innerClass(value=x)
v.save() # we have to save to create the id.
return v
def _params_from_kwargs(self, filter, **kwargs):
"""Given a dictionary of arguments, build a string which
represents it as a parameter list, and filter out any
keywords in filter."""
params = ""
for key in kwargs:
if key not in filter:
value = kwargs[key]
params += "%s=%s, " % (key, value.__repr__())
return params[:-2] # chop off the last ', '
class Person(models.Model):
name = models.CharField(max_length=120)
friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)
Ultimately I think this would still be better if it were pushed deeper into the django code and worked more like the ManyToManyField or the ForeignKey.
I think what you want is a custom model field.

Categories

Resources