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.
Related
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
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 ??
I have a Django template that has data from a few different model types combining to make it. A dashboard if you will. And each of those has an edit form.
Is it best to process all those forms in one view as they are posted back to the same place and differentiating between them by a unique field like below?
Or if having lots of different dedicated avenues is the way forward? Thanks for any guidance
class ProjectDetail(DetailView):
template_name = 'project/view.html'
def get_object(self):
try:
return Project.objects.filter(brief__slug=self.kwargs['slug']).filter(team=get_user_team(self.request)).first()
# add loop to allow multiple teams to work on the same brief (project)
except Exception as e:
project_error = '%s (%s)' % (e.message, type(e))
messages.error(self.request, 'OH NO! %s' % project_error)
return redirect('home')
def get_context_data(self, **kwargs):
project = self.get_object()
context = dict()
context['project'] = project
context['milestone_form'] = MilestoneForm(initial={'project': project})
context['view'] = self
return context
def post(self, request, *args, **kwargs):
# get the context for the page
context = self.get_context_data()
try:
# switch for each of the form types on the team profile page (shown if member)
if 'milestone_form_submit' in request.POST:
project=self.get_object()
# set date arbitrarily to half way to brief deadline
brief = Brief.objects.get(project=project)
last_milestone = self.milestones().last()
milestone_del_date = last_milestone.del_date + timedelta(days=7)
new_milestone = Milestone(
project=project,
title_text=request.POST.get('title_text'),
del_date=milestone_del_date,
)
try:
new_milestone.save()
messages.success(self.request, "Excellent! New delivery popped on the bottom of the list")
except Exception as e:
# pass the erroring form back in the context if not
form = MilestoneForm(request.POST)
context['milestone_form'] = form
messages.error(self.request, "OH NO! Deadline didn't save. Be a sport and check what you entered")
elif 'milestone-edit-date-form-submit' in request.POST:
# get object from db
milestone = Milestone.objects.get(pk=request.POST['id'])
# update del_date field sent
milestone.del_date = request.POST['del_date']
# save back to db
milestone.save()
messages.success(self.request, "Updated that delivery right there!")
elif ...
except Exception as e:
messages.error(self.request, "OH NO! Deadline didn't save. Be a sport and check what you entered")
return render(request, self.template_name, context)
You can use mixins in order to solve your problem.
Example from the gist https://gist.github.com/michelts/1029336
class MultipleFormsMixin(FormMixin):
"""
A mixin that provides a way to show and handle several forms in a
request.
"""
form_classes = {} # set the form classes as a mapping
def get_form_classes(self):
return self.form_classes
def get_forms(self, form_classes):
return dict([(key, klass(**self.get_form_kwargs())) \
for key, klass in form_classes.items()])
def forms_valid(self, forms):
return super(MultipleFormsMixin, self).form_valid(forms)
def forms_invalid(self, forms):
return self.render_to_response(self.get_context_data(forms=forms))
As you can see, when you inherit from this class, you can handle multiple forms simultaneously. Look at the gist's code and adapt it to your problem.
Look at this answer
Im using django 1.10 + Apache in Linux.
I've created a small webapp to upload documents (with dropzone.js) and want to implement the ability for a user to specify who can view/modify/delete a specific file but i can't figure out a way how. I attempted using a ManyToManyField but maybe im not understading the Field itself correctly.
The "Document" model is this:
Model
class Document(models.Model):
file = models.FileField(upload_to = 'files/')
#validators=[validate_file_type])
uploaded_at = models.DateTimeField(auto_now_add = True)
extension = models.CharField(max_length = 30, blank = True)
thumbnail = models.ImageField(blank = True, null = True)
is_public = models.BooleanField(default = False)
accesible_by = models.ManyToManyField(User) #This is my attempt at doing this task.
def clean(self):
self.extension = self.file.name.split('/')[-1].split('.')[-1]
if self.extension == 'xlsx' or self.extension == 'xls':
self.thumbnail = 'xlsx.png'
elif self.extension == 'pptx' or self.extension == 'ppt':
self.thumbnail = 'pptx.png'
elif self.extension == 'docx' or self.extension == 'doc':
self.thumbnail = 'docx.png'
def delete(self, *args, **kwargs):
#delete file from /media/files
self.file.delete(save = False)
#call parent delete method.
super().delete(*args, **kwargs)
#Redirect to file list page.
def get_absolute_url(self):
return reverse('dashby-files:files')
def __str__(self):
return self.file.name.split('/')[-1]
class Meta():
ordering = ['-uploaded_at']
My View to handle the creation of documents:
View
class DocumentCreate(CreateView):
model = Document
fields = ['file', 'is_public']
def form_valid(self, form):
self.object = form.save(commit = False)
## I guess here i would Add the (self.request.user) to the accesible_by Field.
self.object.save()
data = {'status': 'success'}
response = JSONResponse(data, mimetype =
response_mimetype(self.request))
return response
Thanks in advance to anyone for any ideas or suggestions...
You have a model and a view that hopefully works for adding new documents, you still have a number of steps to go.
You'll need a place to assign users that can view/modify/delete your files. If you need to store access levels (view/delete...), your accessible_by will not suffice and you'll do well with a through table to add more information like access level.
You need to write views for various actions like view, delete... that users will request and here you ensure users have the right privileges. An implementation would be to get the request.user and the document id, look up if the user has the permission for what she's doing, return an http unauthorized exception or allow the action to proceed.
Edit: My question is about how can I assign user-permissions to each
individual file
If we're keeping this to access control from the django level, using the document model you already have, and you've taken some steps and for every document, you can assign users (accessible_by). Something like this can get you started:
from django.core.exceptions import PermissionDenied
def view_document(request, doc_pk):
doc = get_object_or_404(Document, pk=doc_pk)
if not doc.accessible_by.filter(username=request.user.username):
raise PermissionDenied
#perform rest of action
Or do you mean to use the permissions framework itself?
I've got a SessionWizardView process with conditional extra steps which at the end of the first step essentially asks 'do you want to add another person' so my condition is generated by checking the cleaned data for the previous step;
def function_factory(prev_step):
""" Creates the functions for the condition dict controlling the additional
entrant steps in the process.
:param prev_step: step in the signup process to check
:type prev_step: unicode
:return: additional_entrant()
:rtype:
"""
def additional_entrant(wizard):
"""
Checks the cleaned_data for the previous step to see if another entrant
needs to be added
"""
# try to get the cleaned data of prev_step
cleaned_data = wizard.get_cleaned_data_for_step(prev_step) or {}
# check if the field ``add_another_person`` was checked.
return cleaned_data.get(u'add_another_person', False)
return additional_entrant
def make_condition_stuff(extra_steps, last_step_before_repeat):
cond_funcs = {}
cond_dict = {}
form_lst = [
(u"main_entrant", EntrantForm),
]
for x in range(last_step_before_repeat, extra_steps):
key1 = u"{}_{}".format(ADDITIONAL_STEP_NAME, x)
if x == 1:
prev_step = u"main_entrant"
else:
prev_step = u"{}_{}".format(ADDITIONAL_STEP_NAME, x-1)
cond_funcs[key1] = function_factory(prev_step)
cond_dict[key1] = cond_funcs[key1]
form_lst.append(
(key1, AdditionalEntrantForm)
)
form_lst.append(
(u"terms", TermsForm)
)
return cond_funcs, cond_dict, form_lst
last_step_before_extras = 1
extra_steps = settings.ADDITIONAL_ENTRANTS
cond_funcs, cond_dict, form_list = make_condition_stuff(
extra_steps,
last_step_before_extras
)
I've also got a dictionary which stores step data behind a key accessible via the session cookie, which also holds a list of the people's details entered by a user. After the first form, this list is rendered as a select box & on selection triggers an Ajax call to the SessionWizard with kwargs which triggers the call to a method which returns a JsonResponse;
class SignupWizard(SessionWizardView):
template_name = 'entrant/wizard_form.html'
form_list = form_list
condition_dict = cond_dict
model = Entrant
main_entrant = None
data_dict = dict()
def get_data(self, source_step, step):
session_data_dict = self.get_session_data_dict()
try:
data = session_data_dict[source_step].copy()
data['event'] = self.current_event.id
for key in data.iterkeys():
if step not in key:
newkey = u'{}-{}'.format(step, key)
data[newkey] = data[key]
del data[key]
except (KeyError, RuntimeError):
data = dict()
data['error'] = (
u'There was a problem retrieving the data you requested. '
u'Please resubmit the form if you would like to try again.'
)
response = JsonResponse(data)
return response
def dispatch(self, request, *args, **kwargs):
response = super(SignupWizard, self).dispatch(
request, *args, **kwargs
)
if 'get_data' in kwargs:
data_id = kwargs['get_data']
step = kwargs['step']
response = self.get_data(data_id, step)
# update the response (e.g. adding cookies)
self.storage.update_response(response)
return response
def process_step(self, form):
form_data = self.get_form_step_data(form)
current_step = self.storage.current_step or ''
session_data_dict = self.get_session_data_dict()
if current_step in session_data_dict:
# Always replace the existing data for a step.
session_data_dict.pop(current_step)
if not isinstance(form, TermsForm):
entrant_data = dict()
fields_to_remove = [
'email', 'confirm_email', 'password',
'confirm_password', 'csrfmiddlewaretoken'
]
for k, v in form_data.iteritems():
entrant_data[k] = v
for field in fields_to_remove:
if '{}-{}'.format(current_step, field) in entrant_data:
entrant_data.pop('{}-{}'.format(current_step, field))
if '{}'.format(field) in entrant_data:
entrant_data.pop('{}'.format(field))
for k in entrant_data.iterkeys():
new_key = re.sub('{}-'.format(current_step), u'', k)
entrant_data[new_key] = entrant_data.pop(k)
session_data_dict[current_step] = entrant_data
done = False
for i, data in enumerate(session_data_dict['data_list']):
if data[0] == current_step:
session_data_dict['data_list'][i] = (
current_step, u'{} {}'.format(
entrant_data['first_name'],
entrant_data['last_name']
)
)
done = True
if not done:
session_data_dict['data_list'].append(
(
current_step, u'{} {}'.format(
entrant_data['first_name'],
entrant_data['last_name']
)
)
)
return form_data
If you step through the form without triggering the Ajax call then the form submits and the condition dict behaves as expected. But if the Ajax is triggered and the data is returned to the form, once you submit the form the session data appears to have gone. Is there a way to change the way I've got this setup so that get_data() can return the data to the page, without destroying the session?
I've got the SESSION_ENGINE set to cached_db on a dev server but I've got an issue whereby when you submit the first conditional form and the system calls get_next_step() followed by get_form_list() and the condition check no longer returns that first conditional form so I'm left with my default form list and a ValueError raised because current_step is no longer part of form_list.
So, to recap, I step through my first form, trigger the first conditional form using the 'add_another_person' field which renders the form as expected at which point form_list looks like this;
form_list
u'main_entrant' <class 'online_entry.forms.EntrantForm'>
u'additional_entrant_1' <class 'online_entry.forms.EntrantForm'>
u'terms' <class 'online_entry.forms.TermsForm'>
But as soon as additional_entrant_1 triggers the Ajax method, then is submitted, the form_list runs through the condition dict & looks like this;
form_list
u'main_entrant' <class 'online_entry.forms.EntrantForm'>
u'terms' <class 'online_entry.forms.TermsForm'>
Could this be an issue with the session storage or the session becoming invalid?
I always overlook the simple explanation.
The get() request of the SessionWizardView resets the storage, and the Ajax call I was making hit the view as a get request, resetting the storage, but also passing back my information.
So with a simple override of the get() method I've resolved this;
def get(self, request, *args, **kwargs):
if 'get_data' in kwargs:
data_id = kwargs['get_data']
step = kwargs['step']
response = self.get_data(data_id, step)
else:
self.storage.reset()
# reset the current step to the first step.
self.storage.current_step = self.steps.first
response = self.render(self.get_form())
return response