Compare commits

..

4 Commits

5 changed files with 60 additions and 58 deletions

View File

@ -1,12 +1,13 @@
from functools import partial from functools import partial
from django.contrib import admin from django.contrib import admin
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mass_mail from django.core.mail import send_mass_mail
from django.shortcuts import render from django.shortcuts import render
from django.utils.translation import ngettext, gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from .. import settings from .. import settings
from .decorators import action_with_confirmation from .decorators import action_with_confirmation
from .forms import SendEmailForm from .forms import SendEmailForm
@ -18,7 +19,7 @@ class SendEmail(object):
template = 'admin/orchestra/generic_confirmation.html' template = 'admin/orchestra/generic_confirmation.html'
default_from = settings.ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL default_from = settings.ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL
__name__ = 'semd_email' __name__ = 'semd_email'
def __call__(self, modeladmin, request, queryset): def __call__(self, modeladmin, request, queryset):
""" make this monster behave like a function """ """ make this monster behave like a function """
self.modeladmin = modeladmin self.modeladmin = modeladmin
@ -34,10 +35,10 @@ class SendEmail(object):
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
} }
return self.write_email(request) return self.write_email(request)
def write_email(self, request): def write_email(self, request):
if not request.user.is_superuser: if not request.user.is_superuser:
raise PermissionDenied raise PermissionDenied()
initial={ initial={
'email_from': self.default_from, 'email_from': self.default_from,
'to': ' '.join(self.get_email_addresses()) 'to': ' '.join(self.get_email_addresses())
@ -51,7 +52,7 @@ class SendEmail(object):
'extra_to': form.cleaned_data['extra_to'], 'extra_to': form.cleaned_data['extra_to'],
'subject': form.cleaned_data['subject'], 'subject': form.cleaned_data['subject'],
'message': form.cleaned_data['message'], 'message': form.cleaned_data['message'],
} }
return self.confirm_email(request, **options) return self.confirm_email(request, **options)
self.context.update({ self.context.update({
@ -62,10 +63,10 @@ class SendEmail(object):
}) })
# Display confirmation page # Display confirmation page
return render(request, self.template, self.context) return render(request, self.template, self.context)
def get_email_addresses(self): def get_email_addresses(self):
return self.queryset.values_list('email', flat=True) return self.queryset.values_list('email', flat=True)
def confirm_email(self, request, **options): def confirm_email(self, request, **options):
email_from = options['email_from'] email_from = options['email_from']
extra_to = options['extra_to'] extra_to = options['extra_to']
@ -88,7 +89,7 @@ class SendEmail(object):
) )
self.modeladmin.message_user(request, msg) self.modeladmin.message_user(request, msg)
return None return None
form = self.form(initial={ form = self.form(initial={
'email_from': email_from, 'email_from': email_from,
'extra_to': ', '.join(extra_to), 'extra_to': ', '.join(extra_to),

View File

@ -1,16 +1,16 @@
from urllib import parse from urllib import parse
from django import forms from django import forms
from django.urls import re_path as url
from django.contrib import admin, messages from django.contrib import admin, messages
from django.contrib.admin.options import IS_POPUP_VAR from django.contrib.admin.options import IS_POPUP_VAR
from django.contrib.admin.utils import unquote from django.contrib.admin.utils import unquote
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect, Http404, HttpResponse
from django.forms.models import BaseInlineFormSet from django.forms.models import BaseInlineFormSet
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import re_path as url
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.html import escape from django.utils.html import escape
@ -19,14 +19,12 @@ from django.views.decorators.debug import sensitive_post_parameters
from orchestra.models.utils import has_db_field from orchestra.models.utils import has_db_field
from ..utils.python import random_ascii, pairwise from ..utils.python import pairwise, random_ascii
from .forms import AdminPasswordChangeForm from .forms import AdminPasswordChangeForm
#, AdminRawPasswordChangeForm #, AdminRawPasswordChangeForm
#from django.contrib.auth.forms import AdminPasswordChangeForm #from django.contrib.auth.forms import AdminPasswordChangeForm
from .utils import action_to_view from .utils import action_to_view
sensitive_post_parameters_m = method_decorator(sensitive_post_parameters()) sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
@ -37,7 +35,7 @@ class ChangeListDefaultFilter(object):
default_changelist_filters = (('my_nodes', 'True'),) default_changelist_filters = (('my_nodes', 'True'),)
""" """
default_changelist_filters = () default_changelist_filters = ()
def changelist_view(self, request, extra_context=None): def changelist_view(self, request, extra_context=None):
# defaults = [] # defaults = []
# for key, value in self.default_changelist_filters: # for key, value in self.default_changelist_filters:
@ -79,7 +77,7 @@ class EnhaceSearchMixin(object):
if 'password' in lookup: if 'password' in lookup:
return False return False
return True return True
def get_search_results(self, request, queryset, search_term): def get_search_results(self, request, queryset, search_term):
""" allows to specify field <field_name>:<search_term> """ """ allows to specify field <field_name>:<search_term> """
search_fields = self.get_search_fields(request) search_fields = self.get_search_fields(request)
@ -109,7 +107,7 @@ class ChangeViewActionsMixin(object):
""" Makes actions visible on the admin change view page. """ """ Makes actions visible on the admin change view page. """
change_view_actions = () change_view_actions = ()
change_form_template = 'orchestra/admin/change_form.html' change_form_template = 'orchestra/admin/change_form.html'
def get_urls(self): def get_urls(self):
"""Returns the additional urls for the change view links""" """Returns the additional urls for the change view links"""
urls = super(ChangeViewActionsMixin, self).get_urls() urls = super(ChangeViewActionsMixin, self).get_urls()
@ -124,7 +122,7 @@ class ChangeViewActionsMixin(object):
) )
) )
return new_urls + urls return new_urls + urls
def get_change_view_actions(self, obj=None): def get_change_view_actions(self, obj=None):
""" allow customization on modelamdin """ """ allow customization on modelamdin """
views = [] views = []
@ -145,7 +143,7 @@ class ChangeViewActionsMixin(object):
view.hidden = getattr(action, 'hidden', False) view.hidden = getattr(action, 'hidden', False)
views.append(view) views.append(view)
return views return views
def change_view(self, request, object_id, **kwargs): def change_view(self, request, object_id, **kwargs):
if kwargs.get('extra_context', None) is None: if kwargs.get('extra_context', None) is None:
kwargs['extra_context'] = {} kwargs['extra_context'] = {}
@ -165,21 +163,21 @@ class ChangeAddFieldsMixin(object):
change_readonly_fields = () change_readonly_fields = ()
change_form = None change_form = None
add_inlines = None add_inlines = None
def get_prepopulated_fields(self, request, obj=None): def get_prepopulated_fields(self, request, obj=None):
if not obj: if not obj:
return super(ChangeAddFieldsMixin, self).get_prepopulated_fields(request, obj) return super(ChangeAddFieldsMixin, self).get_prepopulated_fields(request, obj)
return {} return {}
def get_change_readonly_fields(self, request, obj=None): def get_change_readonly_fields(self, request, obj=None):
return self.change_readonly_fields return self.change_readonly_fields
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj) fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj)
if obj: if obj:
return fields + self.get_change_readonly_fields(request, obj) return fields + self.get_change_readonly_fields(request, obj)
return fields return fields
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
if not obj: if not obj:
if self.add_fieldsets: if self.add_fieldsets:
@ -187,7 +185,7 @@ class ChangeAddFieldsMixin(object):
elif self.add_fields: elif self.add_fields:
return [(None, {'fields': self.add_fields})] return [(None, {'fields': self.add_fields})]
return super(ChangeAddFieldsMixin, self).get_fieldsets(request, obj) return super(ChangeAddFieldsMixin, self).get_fieldsets(request, obj)
def get_inline_instances(self, request, obj=None): def get_inline_instances(self, request, obj=None):
""" add_inlines and inline.parent_object """ """ add_inlines and inline.parent_object """
if obj: if obj:
@ -198,7 +196,7 @@ class ChangeAddFieldsMixin(object):
for inline in inlines: for inline in inlines:
inline.parent_object = obj inline.parent_object = obj
return inlines return inlines
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
""" Use special form during user creation """ """ Use special form during user creation """
defaults = {} defaults = {}
@ -218,13 +216,13 @@ class ExtendedModelAdmin(ChangeViewActionsMixin,
EnhaceSearchMixin, EnhaceSearchMixin,
admin.ModelAdmin): admin.ModelAdmin):
list_prefetch_related = None list_prefetch_related = None
def get_queryset(self, request): def get_queryset(self, request):
qs = super(ExtendedModelAdmin, self).get_queryset(request) qs = super(ExtendedModelAdmin, self).get_queryset(request)
if self.list_prefetch_related: if self.list_prefetch_related:
qs = qs.prefetch_related(*self.list_prefetch_related) qs = qs.prefetch_related(*self.list_prefetch_related)
return qs return qs
def get_object(self, request, object_id, from_field=None): def get_object(self, request, object_id, from_field=None):
obj = super(ExtendedModelAdmin, self).get_object(request, object_id, from_field) obj = super(ExtendedModelAdmin, self).get_object(request, object_id, from_field)
if obj is None: if obj is None:
@ -237,7 +235,7 @@ class ExtendedModelAdmin(ChangeViewActionsMixin,
class ChangePasswordAdminMixin(object): class ChangePasswordAdminMixin(object):
change_password_form = AdminPasswordChangeForm change_password_form = AdminPasswordChangeForm
change_user_password_template = 'admin/orchestra/change_password.html' change_user_password_template = 'admin/orchestra/change_password.html'
def get_urls(self): def get_urls(self):
opts = self.model._meta opts = self.model._meta
info = opts.app_label, opts.model_name info = opts.app_label, opts.model_name
@ -249,14 +247,14 @@ class ChangePasswordAdminMixin(object):
self.admin_site.admin_view(self.show_hash), self.admin_site.admin_view(self.show_hash),
name='%s_%s_show_hash' % info) name='%s_%s_show_hash' % info)
] + super().get_urls() ] + super().get_urls()
def get_change_password_username(self, obj): def get_change_password_username(self, obj):
return str(obj) return str(obj)
@sensitive_post_parameters_m @sensitive_post_parameters_m
def change_password(self, request, id, form_url=''): def change_password(self, request, id, form_url=''):
if not self.has_change_permission(request): if not self.has_change_permission(request):
raise PermissionDenied raise PermissionDenied()
# TODO use this insetad of self.get_object(), in other places # TODO use this insetad of self.get_object(), in other places
obj = get_object_or_404(self.get_queryset(request), pk=id) obj = get_object_or_404(self.get_queryset(request), pk=id)
raw = request.GET.get('raw', '0') == '1' raw = request.GET.get('raw', '0') == '1'
@ -281,7 +279,7 @@ class ChangePasswordAdminMixin(object):
for rel in account.get_related_passwords(db_field=raw): for rel in account.get_related_passwords(db_field=raw):
if not isinstance(obj, type(rel)): if not isinstance(obj, type(rel)):
related.append(rel) related.append(rel)
if request.method == 'POST': if request.method == 'POST':
form = self.change_password_form(obj, request.POST, related=related, raw=raw) form = self.change_password_form(obj, request.POST, related=related, raw=raw)
if form.is_valid(): if form.is_valid():
@ -293,7 +291,7 @@ class ChangePasswordAdminMixin(object):
return HttpResponseRedirect('..') return HttpResponseRedirect('..')
else: else:
form = self.change_password_form(obj, related=related, raw=raw) form = self.change_password_form(obj, related=related, raw=raw)
fieldsets = [ fieldsets = [
(obj._meta.verbose_name.capitalize(), { (obj._meta.verbose_name.capitalize(), {
'classes': ('wide',), 'classes': ('wide',),
@ -305,7 +303,7 @@ class ChangePasswordAdminMixin(object):
'classes': ('wide',), 'classes': ('wide',),
'fields': ('password_%i' % ix,) if raw else ('password1_%i' % ix, 'password2_%i' % ix) 'fields': ('password_%i' % ix,) if raw else ('password1_%i' % ix, 'password2_%i' % ix)
})) }))
obj_username = self.get_change_password_username(obj) obj_username = self.get_change_password_username(obj)
adminForm = admin.helpers.AdminForm(form, fieldsets, {}) adminForm = admin.helpers.AdminForm(form, fieldsets, {})
context = { context = {
@ -331,9 +329,9 @@ class ChangePasswordAdminMixin(object):
} }
context.update(admin.site.each_context(request)) context.update(admin.site.each_context(request))
return TemplateResponse(request, self.change_user_password_template, context) return TemplateResponse(request, self.change_user_password_template, context)
def show_hash(self, request, id): def show_hash(self, request, id):
if not request.user.is_superuser: if not request.user.is_superuser:
raise PermissionDenied raise PermissionDenied()
obj = get_object_or_404(self.get_queryset(request), pk=id) obj = get_object_or_404(self.get_queryset(request), pk=id)
return HttpResponse(obj.password) return HttpResponse(obj.password)

View File

@ -143,6 +143,8 @@ DATABASES = {
} }
} }
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# Password validation # Password validation
# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#auth-password-validators
@ -233,6 +235,7 @@ FLUENT_DASHBOARD_ICON_THEME = '../orchestra/icons'
# Django-celery # Django-celery
import djcelery import djcelery
djcelery.setup_loader() djcelery.setup_loader()
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'

View File

@ -1,14 +1,14 @@
import sys import sys
from contextlib import ContextDecorator
from threading import local from threading import local
from django.contrib.admin.models import LogEntry from django.contrib.admin.models import LogEntry
from django.db.models.signals import pre_delete, post_save, m2m_changed from django.db.models.signals import m2m_changed, post_save, pre_delete
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.decorators import ContextDecorator
from orchestra.utils.python import OrderedSet from orchestra.utils.python import OrderedSet
from . import manager, Operation, helpers from . import Operation, helpers, manager
from .middlewares import OperationsMiddleware from .middlewares import OperationsMiddleware
from .models import BackendLog, BackendOperation from .models import BackendLog, BackendOperation
@ -37,7 +37,7 @@ def m2m_collector(sender, *args, **kwargs):
class orchestrate(ContextDecorator): class orchestrate(ContextDecorator):
""" """
Context manager for triggering backend operations out of request-response cycle, e.g. shell Context manager for triggering backend operations out of request-response cycle, e.g. shell
with orchestrate(): with orchestrate():
user = SystemUser.objects.get(username='rata') user = SystemUser.objects.get(username='rata')
user.shell = '/dev/null' user.shell = '/dev/null'
@ -46,7 +46,7 @@ class orchestrate(ContextDecorator):
thread_locals = local() thread_locals = local()
thread_locals.pending_operations = None thread_locals.pending_operations = None
thread_locals.route_cache = None thread_locals.route_cache = None
@classmethod @classmethod
def collect(cls, action, **kwargs): def collect(cls, action, **kwargs):
""" Collects all pending operations derived from model signals """ """ Collects all pending operations derived from model signals """
@ -57,14 +57,14 @@ class orchestrate(ContextDecorator):
kwargs['route_cache'] = cls.thread_locals.route_cache kwargs['route_cache'] = cls.thread_locals.route_cache
instance = kwargs.pop('instance') instance = kwargs.pop('instance')
manager.collect(instance, action, **kwargs) manager.collect(instance, action, **kwargs)
def __enter__(self): def __enter__(self):
cls = type(self) cls = type(self)
self.old_pending_operations = cls.thread_locals.pending_operations self.old_pending_operations = cls.thread_locals.pending_operations
cls.thread_locals.pending_operations = OrderedSet() cls.thread_locals.pending_operations = OrderedSet()
self.old_route_cache = cls.thread_locals.route_cache self.old_route_cache = cls.thread_locals.route_cache
cls.thread_locals.route_cache = {} cls.thread_locals.route_cache = {}
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
cls = type(self) cls = type(self)
if not exc_type: if not exc_type:

View File

@ -1,23 +1,23 @@
Django==2.2.24 Django==3.2.23
django-fluent-dashboard==1.0.1 django-fluent-dashboard==2.0
django-admin-tools==0.9.1 django-admin-tools==0.9.3
django-extensions==3.1.3 django-extensions==3.2.3
django-celery==3.2.1 django-celery==3.3.1
celery==3.1.23 celery<4.0,>=3.1.15
kombu==3.0.35 kombu==3.0.37
billiard==3.3.0.23 billiard==3.3.0.23
Markdown==3.3.4 Markdown==3.5.1
djangorestframework==3.12.4 djangorestframework==3.14.0
Pygments==2.9.0 Pygments==2.17.2
django-filter==2.4.0 django-filter==23.4
jsonfield==3.1.0 jsonfield==3.1.0
python-dateutil>=2.7.0 python-dateutil==2.8.2
passlib==1.7.4 passlib==1.7.4
django-iban==0.3.0 django-iban==0.3.1
requests requests
phonenumbers==8.12.27 phonenumbers==8.13.26
django-countries django-countries
django-localflavor==3.1 django-localflavor==4.0
amqp amqp
anyjson anyjson
pytz pytz