From 6d9f519241f34d9d636c44b3a794e01d296c96df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=9F=B9=E6=B7=87?= Date: Tue, 2 Aug 2016 20:56:48 +0800 Subject: [PATCH 001/218] fiexd quick-form file upload bug #47 --- .../xadmin/js/xadmin.plugin.quick-form.js | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/xadmin/static/xadmin/js/xadmin.plugin.quick-form.js b/xadmin/static/xadmin/js/xadmin.plugin.quick-form.js index 779300d53..9bfcb5130 100644 --- a/xadmin/static/xadmin/js/xadmin.plugin.quick-form.js +++ b/xadmin/static/xadmin/js/xadmin.plugin.quick-form.js @@ -90,7 +90,7 @@ }, this)) .fail($.proxy(function(xhr) { this.$mask.hide(); - alert(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!'); + alert(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!'); }, this)); } , save: function(newValue) { @@ -108,16 +108,31 @@ // } // }) + var $nonfile_input = this.$form.serializeArray(); + + var formData = new FormData(); + + $nonfile_input.forEach(function(field) { + formData.append(field.name, field.value) + }); + + var $file_input = this.$form.find("input[type=file]"); + $file_input.each(function (idx, file) { + formData.append($(file).attr('name'), file.files[0]); + }); + return $.ajax({ - data: [this.$form.serialize(), $.param(off_check_box)].join('&'), + data: formData, url: this.$form.attr('action'), type: "POST", dataType: 'json', + contentType: false, + processData: false, beforeSend: function(xhr, settings) { xhr.setRequestHeader("X-CSRFToken", $.getCookie('csrftoken')); } }) - }, + }, } $.fn.ajaxform = function ( option ) { @@ -167,7 +182,7 @@ if(!this.modal){ var modal = $(' From 515ee222eb572cc162e30a1d2320e40a4c522efe Mon Sep 17 00:00:00 2001 From: Li Dongyong Date: Fri, 16 Mar 2018 10:29:49 +0800 Subject: [PATCH 036/218] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAdminSplitDateTime=20?= =?UTF-8?q?IndexError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xadmin/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xadmin/widgets.py b/xadmin/widgets.py index d874ec726..ea6050a4e 100644 --- a/xadmin/widgets.py +++ b/xadmin/widgets.py @@ -73,7 +73,8 @@ def __init__(self, attrs=None): def render(self, name, value, attrs=None): if DJANGO_11: - input_html = [ht for ht in super(AdminSplitDateTime, self).render(name, value, attrs).split('\n') if ht != ''] + input_html = [ht for ht in super(AdminSplitDateTime, self).render(name, value, attrs).replace( + '/>\n
%s' '
' From 9ea35766d58e75f972df6d13c52bb95e948da80f Mon Sep 17 00:00:00 2001 From: scrapy <410178050@qq.com> Date: Tue, 27 Mar 2018 12:18:10 +0800 Subject: [PATCH 037/218] Update logged_out.html Repair click button cannot be closed --- xadmin/templates/xadmin/views/logged_out.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xadmin/templates/xadmin/views/logged_out.html b/xadmin/templates/xadmin/views/logged_out.html index a2efdc6b7..ffe0c7c08 100644 --- a/xadmin/templates/xadmin/views/logged_out.html +++ b/xadmin/templates/xadmin/views/logged_out.html @@ -16,7 +16,7 @@

{% trans "Logout Success" %}

{% trans "Thanks for spending some quality time with the Web site today." %}

- {% trans 'Close Window' %} + {% trans 'Close Window' %} {% trans 'Log in again' %}

From 42491c879f05fbc7b0cdb664e929b9f8303b5893 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 15 May 2018 12:33:50 -0300 Subject: [PATCH 038/218] Improvements in api to work with dynamically selected actions. --- xadmin/plugins/actions.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xadmin/plugins/actions.py b/xadmin/plugins/actions.py index 02085bf33..71a1875fe 100644 --- a/xadmin/plugins/actions.py +++ b/xadmin/plugins/actions.py @@ -223,8 +223,11 @@ def get_actions(self): class_actions = getattr(klass, 'actions', []) if not class_actions: continue - actions.extend( - [self.get_action(action) for action in class_actions]) + # Allows additional processing for actions. + # May need to select action per user profile. + if callable(class_actions): + class_actions = class_actions(self.admin_view.request) + actions.extend([self.get_action(action) for action in class_actions]) # get_action might have returned None, so filter any of those out. actions = filter(None, actions) From dadb99ab091870dabb7dbefa012276de3f810447 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 22 May 2018 18:03:31 -0300 Subject: [PATCH 039/218] Removing unused code. --- xadmin/views/edit.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/xadmin/views/edit.py b/xadmin/views/edit.py index a52ea4d26..61a3162c1 100644 --- a/xadmin/views/edit.py +++ b/xadmin/views/edit.py @@ -183,12 +183,6 @@ def get_model_form(self, **kwargs): return modelform_factory(self.model, **defaults) - try: - return modelform_factory(self.model, **defaults) - except FieldError as e: - raise FieldError('%s. Check fields/fieldsets/exclude attributes of class %s.' - % (e, self.__class__.__name__)) - @filter_hook def get_form_layout(self): layout = copy.deepcopy(self.form_layout) From cb911c76ef3a48a22cc0ee3eb374bcb9bcadf89d Mon Sep 17 00:00:00 2001 From: alexsilva Date: Tue, 3 Jul 2018 11:38:34 -0300 Subject: [PATCH 040/218] New translation --- xadmin/locale/pt_BR/LC_MESSAGES/django.mo | Bin 22566 -> 22612 bytes xadmin/locale/pt_BR/LC_MESSAGES/django.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/xadmin/locale/pt_BR/LC_MESSAGES/django.mo b/xadmin/locale/pt_BR/LC_MESSAGES/django.mo index 4799e32fc747bf3df92ae2034567301efd7cbdb7..3b13cea85b54a35408b4b4442aebaa156ea34413 100644 GIT binary patch delta 5750 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 delta 5710 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 diff --git a/xadmin/locale/pt_BR/LC_MESSAGES/django.po b/xadmin/locale/pt_BR/LC_MESSAGES/django.po index 7b5565ae1..4fcb6bfd3 100644 --- a/xadmin/locale/pt_BR/LC_MESSAGES/django.po +++ b/xadmin/locale/pt_BR/LC_MESSAGES/django.po @@ -910,7 +910,7 @@ msgstr "A cada %(t)s segundos" #: .\templates\xadmin\blocks\model_list.top_toolbar.saveorder.html:4 msgid "Save Order" -msgstr "" +msgstr "Salvar ordenação" #: .\templates\xadmin\edit_inline\blank.html:5 .\views\detail.py:23 #: .\views\edit.py:102 .\views\list.py:29 From 9dddc4d6af970d789277f6e84a87eb664e882bb3 Mon Sep 17 00:00:00 2001 From: alexsilva Date: Tue, 3 Jul 2018 11:40:54 -0300 Subject: [PATCH 041/218] Add icon sotablelist; Change buttom theme. --- .../xadmin/blocks/model_list.top_toolbar.saveorder.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xadmin/templates/xadmin/blocks/model_list.top_toolbar.saveorder.html b/xadmin/templates/xadmin/blocks/model_list.top_toolbar.saveorder.html index 6c85f2e22..ceca7b8b7 100644 --- a/xadmin/templates/xadmin/blocks/model_list.top_toolbar.saveorder.html +++ b/xadmin/templates/xadmin/blocks/model_list.top_toolbar.saveorder.html @@ -1,6 +1,6 @@ {% load i18n %} From 4e08412d1ff29dc666bf19327a68e3a3e68459d7 Mon Sep 17 00:00:00 2001 From: alexsilva Date: Tue, 3 Jul 2018 11:54:48 -0300 Subject: [PATCH 042/218] Fix context. --- xadmin/plugins/sortablelist.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xadmin/plugins/sortablelist.py b/xadmin/plugins/sortablelist.py index 8b9942f6e..fe49324c0 100644 --- a/xadmin/plugins/sortablelist.py +++ b/xadmin/plugins/sortablelist.py @@ -11,6 +11,7 @@ from django.core.urlresolvers import reverse from django.db import transaction +from xadmin.plugins.utils import get_context_dict from xadmin.views import ( BaseAdminPlugin, ModelAdminView, ListAdminView ) @@ -47,7 +48,8 @@ def get_context(self, context): def block_top_toolbar(self, context, nodes): save_node = render_to_string( - 'xadmin/blocks/model_list.top_toolbar.saveorder.html', context_instance=context + 'xadmin/blocks/model_list.top_toolbar.saveorder.html', + context=get_context_dict(context) ) nodes.append(save_node) From f3dab3f8336c099b2d5f475ec14d294ce2ef7dc5 Mon Sep 17 00:00:00 2001 From: alexsilva Date: Tue, 3 Jul 2018 12:32:54 -0300 Subject: [PATCH 043/218] Add support to custom columns. --- xadmin/plugins/sortablelist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xadmin/plugins/sortablelist.py b/xadmin/plugins/sortablelist.py index fe49324c0..47d764fc2 100644 --- a/xadmin/plugins/sortablelist.py +++ b/xadmin/plugins/sortablelist.py @@ -22,6 +22,8 @@ class SortableListPlugin(BaseAdminPlugin): list_order_field = None + # Used in custom columns + list_order_display_field = None def init_request(self, *args, **kwargs): return bool(self.list_order_field) @@ -38,7 +40,8 @@ def result_row(self, __, obj): return row def result_item(self, item, obj, field_name, row): - if self.is_list_sortable and field_name == self.list_order_field: + if self.is_list_sortable and field_name in (self.list_order_field, + self.list_order_display_field): item.btns.append('') return item From a872cbdd1bfd8ff489cae7ea33ec9f02f5fa7968 Mon Sep 17 00:00:00 2001 From: alexsilva Date: Wed, 11 Jul 2018 14:21:19 -0300 Subject: [PATCH 044/218] Fixing broken file upload layout. --- xadmin/widgets.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xadmin/widgets.py b/xadmin/widgets.py index 3263ddc2a..0b6d101cf 100644 --- a/xadmin/widgets.py +++ b/xadmin/widgets.py @@ -169,10 +169,7 @@ def __init__(self, attrs=None): class AdminFileWidget(forms.ClearableFileInput): - template_with_initial = (u'

%s

' - % forms.ClearableFileInput.initial_text) - template_with_clear = (u'%s' - % forms.ClearableFileInput.clear_checkbox_label) + """For compatibility""" class AdminTextareaWidget(forms.Textarea): From cf031960c32529b6bf55826266a3e480ad606432 Mon Sep 17 00:00:00 2001 From: alexsilva Date: Tue, 14 Aug 2018 20:31:16 -0300 Subject: [PATCH 045/218] Fix inline fields setup. --- xadmin/plugins/inline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xadmin/plugins/inline.py b/xadmin/plugins/inline.py index c95928553..a71ff6807 100644 --- a/xadmin/plugins/inline.py +++ b/xadmin/plugins/inline.py @@ -164,7 +164,7 @@ def get_formset(self, **kwargs): "form": self.form, "formset": self.formset, "fk_name": self.fk_name, - 'fields': forms.ALL_FIELDS, + 'fields': forms.ALL_FIELDS if len(self.fields) == 0 else self.fields, "exclude": exclude, "formfield_callback": self.formfield_for_dbfield, "extra": self.extra, From 9b79d83e825432b93d5756820608c12c67a09656 Mon Sep 17 00:00:00 2001 From: alexsilva Date: Fri, 17 Aug 2018 18:32:33 -0300 Subject: [PATCH 046/218] Fix html sintax. --- xadmin/templates/xadmin/edit_inline/tabular.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xadmin/templates/xadmin/edit_inline/tabular.html b/xadmin/templates/xadmin/edit_inline/tabular.html index c38522a74..797f2c051 100644 --- a/xadmin/templates/xadmin/edit_inline/tabular.html +++ b/xadmin/templates/xadmin/edit_inline/tabular.html @@ -10,7 +10,8 @@ {% for field in fields %} {% if not field.widget.is_hidden %} {{ field.label|capfirst }} - {% if field.help_text %} {% endif %} + {% if field.help_text %}  + {% endif %} {% endif %} {% endfor %} From ea0dc8a2fdf97095dd9d4da87745d19d8c4ce109 Mon Sep 17 00:00:00 2001 From: alexsilva Date: Mon, 20 Aug 2018 13:19:33 -0300 Subject: [PATCH 047/218] Option to export files by email. --- xadmin/plugins/export.py | 53 +++++++++++++++++-- .../model_list.top_toolbar.exports.html | 3 ++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/xadmin/plugins/export.py b/xadmin/plugins/export.py index 88b4cadab..36af48d50 100644 --- a/xadmin/plugins/export.py +++ b/xadmin/plugins/export.py @@ -1,6 +1,9 @@ import datetime import sys + +from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.core.mail import EmailMultiAlternatives from django.http import HttpResponse from django.template import loader from django.utils import six @@ -72,6 +75,7 @@ class ExportPlugin(BaseAdminPlugin): export_unicode_csv = False export_unicode_encoding = "utf-8" + export_email_config = {} def init_request(self, *args, **kwargs): return self.request.GET.get('_do_') == 'export' @@ -243,16 +247,55 @@ def get_json_export(self, context): return json.dumps({'objects': results}, ensure_ascii=False, indent=(self.request.GET.get('export_json_format', 'off') == 'on') and 4 or None) + def send_mail(self, request, filename, content, file_mimetype): + """Send the data file by email""" + user = request.user + if not hasattr(user, 'email'): + return + export_email_config = { + 'subject': _('Exported file delivery'), + 'message': _('The file is attached.'), + 'from_email': settings.DEFAULT_FROM_EMAIL, + 'recipient_list': [user.email], + 'fail_silently': True, + 'html_message': False + } + export_email_config.update(self.export_email_config) + + html_message = export_email_config.pop('html_message', False) + fail_silently = export_email_config.pop('fail_silently', True) + + # compat + export_email_config['body'] = export_email_config.pop('message', '') + export_email_config['to'] = export_email_config.pop('recipient_list', None) + + mail = EmailMultiAlternatives(**export_email_config) + if html_message: + mail.attach_alternative(html_message, 'text/html') + + mail.attach(filename, content, file_mimetype) + mail.send(fail_silently=fail_silently) + return True + def get_response(self, response, context, *args, **kwargs): file_type = self.request.GET.get('export_type', 'csv') - response = HttpResponse(content_type="{0:s}; charset={1:s}".format(self.export_mimes[file_type], - self.export_unicode_encoding)) - file_name = self.opts.verbose_name.replace(' ', '_') + to_email = self.request.GET.get('send_mail', 'off') == 'on' + + content = getattr(self, 'get_%s_export' % file_type)(context) + + filename = u"{0:s}.{1:s}".format(self.opts.verbose_name.replace(' ', '_'), + file_type) + + file_mimetype = self.export_mimes[file_type] + + if to_email and self.send_mail(self.admin_view.request, filename, content, file_mimetype): + return HttpResponse('ok') - filename_format = u'attachment; filename={0:s}.{1:s}'.format(file_name, file_type) + response = HttpResponse(content_type="{0:s}; charset={1:s}".format(file_mimetype, self.export_unicode_encoding)) + filename_format = u'attachment; filename="{0:s}"'.format(filename) response['Content-Disposition'] = filename_format.encode(self.export_unicode_encoding) - response.write(getattr(self, 'get_%s_export' % file_type)(context)) + response.write(content) return response # View Methods diff --git a/xadmin/templates/xadmin/blocks/model_list.top_toolbar.exports.html b/xadmin/templates/xadmin/blocks/model_list.top_toolbar.exports.html index d947ffb3d..b3cb1f882 100644 --- a/xadmin/templates/xadmin/blocks/model_list.top_toolbar.exports.html +++ b/xadmin/templates/xadmin/blocks/model_list.top_toolbar.exports.html @@ -42,6 +42,9 @@ + {% endblock %} From 880c8ac31b7c3fe7223446138c048ccf74e9fa7d Mon Sep 17 00:00:00 2001 From: alexsilva Date: Tue, 21 Aug 2018 22:27:27 -0300 Subject: [PATCH 062/218] Optionally send results by email. --- xadmin/plugins/importexport.py | 65 +++++++++++++++++++ ..._list.top_toolbar.importexport.export.html | 9 +++ 2 files changed, 74 insertions(+) diff --git a/xadmin/plugins/importexport.py b/xadmin/plugins/importexport.py index e92e57b63..249c1b50e 100644 --- a/xadmin/plugins/importexport.py +++ b/xadmin/plugins/importexport.py @@ -38,7 +38,11 @@ class FooAdmin(object): ++++++++++++++++ More info about django-import-export please refer https://github.com/django-import-export/django-import-export """ +import threading from datetime import datetime + +from django.conf import settings +from django.core.mail import EmailMultiAlternatives from django.template import loader from xadmin.plugins.utils import get_context_dict from xadmin.sites import site @@ -445,10 +449,61 @@ def block_top_toolbar(self, context, nodes): class ExportPlugin(ExportMixin, BaseAdminPlugin): + export_email_config = {} def init_request(self, *args, **kwargs): return self.request.GET.get('_action_') == 'export' + def send_mail(self, user, request, **kwargs): + """Sends the file with data by email to the user""" + host = request.get_host() + email_config = { + 'subject': _('Exported file delivery'), + 'message': _('Sent from address {0!s}. The file is attached.').format(host), + 'from_email': settings.DEFAULT_FROM_EMAIL, + 'recipient_list': [user.email], + 'fail_silently': True, + 'html_message': False + } + email_config.update(self.export_email_config) + + def send_mail_async(config): + file_format = kwargs['file_format'] + content = self.get_export_data(file_format, + kwargs['queryset'], + request=request) + content_type = file_format.get_content_type() + filename = self.get_export_filename(file_format) + + html_message = config.pop('html_message', False) + fail_silently = config.pop('fail_silently', True) + + # compat + config['body'] = config.pop('message', '') + config['to'] = config.pop('recipient_list', None) + + mail = EmailMultiAlternatives(**config) + if html_message: + mail.attach_alternative(html_message, 'text/html') + + mail.attach(filename, content, content_type) + mail.send(fail_silently=fail_silently) + + thargs = (email_config.copy(),) + th = threading.Thread(target=send_mail_async, args=thargs) + th.start() + + def send_mail_response(self, request, **kwargs): + user = request.user + email = user.email if hasattr(user, 'email') else None + if email is not None: + self.send_mail(user, request, **kwargs) + messages.success(request, _("The file is sent to your email: " + "{0:s}".format(email))) + else: + messages.warning(request, _("Your account does not have an email address.")) + return HttpResponseRedirect(request.path) + def get_response(self, response, context, *args, **kwargs): has_view_perm = self.has_model_perm(self.model, 'view') if not has_view_perm: @@ -467,8 +522,18 @@ def get_response(self, response, context, *args, **kwargs): formats = self.get_export_formats() file_format = formats[int(export_format)]() queryset = self.get_export_queryset(self.request, context) + + # The export takes place in the background, so you do not have to wait. + if self.request.GET.get('result-action', '') == 'sendmail': + return self.send_mail_response( + self.request, + file_format=file_format, + queryset=queryset) + + # Follow normal flow if it is not to send by email export_data = self.get_export_data(file_format, queryset, request=self.request) content_type = file_format.get_content_type() + # Django 1.7 uses the content_type kwarg instead of mimetype try: response = HttpResponse(export_data, content_type=content_type) diff --git a/xadmin/templates/xadmin/blocks/model_list.top_toolbar.importexport.export.html b/xadmin/templates/xadmin/blocks/model_list.top_toolbar.importexport.export.html index 8df02f5c5..de78738dc 100644 --- a/xadmin/templates/xadmin/blocks/model_list.top_toolbar.importexport.export.html +++ b/xadmin/templates/xadmin/blocks/model_list.top_toolbar.importexport.export.html @@ -74,6 +74,15 @@