285 lines
9.7 KiB
Python
285 lines
9.7 KiB
Python
import json
|
|
|
|
from django.contrib import messages
|
|
from django.contrib.admin.utils import NestedObjects
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.db import router
|
|
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
|
|
from django.urls import reverse
|
|
from django.utils.html import escape, escapejs
|
|
from django.utils.text import capfirst
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.views.generic import ListView, CreateView, UpdateView, DeleteView
|
|
|
|
from . import gas_settings
|
|
from . import utils
|
|
|
|
|
|
class AjaxCommandsMixin:
|
|
"""
|
|
Mixin class for views with javascript interaction.
|
|
|
|
Allows defining several commands (POST or GET) on a diferent
|
|
method per command. A request is identified as a command if it has
|
|
a `command` field in the request.
|
|
|
|
To react to a POST command define a method starting with `do_`.
|
|
For example a request with a command named `store_answer` will be
|
|
processed in the method `do_store_answer`.
|
|
|
|
Similarly, to react to a GET command define a method starting with
|
|
`send_`. For example a request with a command named `user_list` will be
|
|
processed in the method `send_user_list`.
|
|
|
|
If the request has no command then it will be processed like in
|
|
any other DJango view.
|
|
|
|
If the request has a command not defined in the view it will get a
|
|
400 response (Bad Request).
|
|
|
|
The helper `render_json` method allows returning an
|
|
application/json with automatic serialization of the given python
|
|
data. It uses the extended json encoder allowing serialization of
|
|
queryets, lazy strings, dates, etc.
|
|
"""
|
|
def post(self, request, *args, **kwargs):
|
|
if 'command' in self.request.POST:
|
|
command_processor = getattr(self, 'do_{0}'.format(self.request.POST['command']), None)
|
|
if command_processor is not None:
|
|
return command_processor()
|
|
else:
|
|
return HttpResponseBadRequest()
|
|
else:
|
|
handler = getattr(super(), 'post', self.http_method_not_allowed)
|
|
return handler(request, *args, **kwargs)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if 'command' in self.request.GET:
|
|
command_processor = getattr(self, 'send_{0}'.format(self.request.GET['command']), None)
|
|
if command_processor is not None:
|
|
return command_processor()
|
|
else:
|
|
return HttpResponseBadRequest()
|
|
else:
|
|
handler = getattr(super(), 'get', self.http_method_not_allowed)
|
|
return handler(request, *args, **kwargs)
|
|
|
|
def render_json(self, data, encoder=utils.JSONEncoder):
|
|
return HttpResponse(
|
|
json.dumps(data, indent=2, cls=encoder),
|
|
content_type='application/json')
|
|
|
|
|
|
class GASMixin:
|
|
base_role = 'admins'
|
|
roles = set()
|
|
base_template = 'gas/base.html'
|
|
cancel_url = None
|
|
continue_url = None
|
|
header_title = ''
|
|
title = ''
|
|
help_text = ''
|
|
success_message = _("Operation successful.")
|
|
breadcrumbs = []
|
|
actions = None
|
|
|
|
def check_user_forbidden(self):
|
|
user = self.request.user
|
|
roles = set(self.roles)
|
|
roles.add(self.base_role)
|
|
access_denied = (
|
|
not user.is_authenticated or (
|
|
not user.is_superuser
|
|
and user.user_roles.filter(role__in=roles).count() == 0
|
|
)
|
|
)
|
|
return access_denied
|
|
|
|
def dispatch(self, *args, **kwargs):
|
|
if self.check_user_forbidden():
|
|
return HttpResponseRedirect(reverse('gas:login') + '?next={}'.format(self.request.path))
|
|
return super().dispatch(*args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
response = super().form_valid(form)
|
|
if 'save_and_continue' in self.request.POST:
|
|
response = HttpResponseRedirect(self.get_continue_url())
|
|
messages.add_message(self.request, messages.SUCCESS, self.get_success_message())
|
|
return response
|
|
|
|
def get_success_message(self):
|
|
return self.success_message
|
|
|
|
def get_home_url(self):
|
|
""" Url for the home of the control panel """
|
|
return reverse('gas:index')
|
|
|
|
def get_cancel_url(self):
|
|
if self.cancel_url:
|
|
# Forcing possible reverse_lazy evaluation
|
|
url = str(self.cancel_url)
|
|
return url
|
|
else:
|
|
return self.get_success_url()
|
|
|
|
def get_continue_url(self):
|
|
if self.continue_url:
|
|
# Forcing possible reverse_lazy evaluation
|
|
url = str(self.continue_url)
|
|
return url
|
|
else:
|
|
raise ImproperlyConfigured("No URL to redirect to. Provide a continue_url.")
|
|
|
|
def get_header_title(self):
|
|
" Contents for the <title> tag. "
|
|
return self.header_title or self.title
|
|
|
|
def get_title(self):
|
|
" Contents for page title. "
|
|
return self.title
|
|
|
|
def get_help_text(self):
|
|
" Contents for page help. "
|
|
return self.help_text
|
|
|
|
def get_breadcrumbs(self):
|
|
" Returns a list of (url, label) tuples for the breadcrumbs "
|
|
return self.breadcrumbs
|
|
|
|
def get_actions(self):
|
|
return self.actions or []
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
css = gas_settings.MEDIA['css']
|
|
js = gas_settings.MEDIA['js']
|
|
if gas_settings.EXTRA_MEDIA:
|
|
css = css + gas_settings.EXTRA_MEDIA.get('css', [])
|
|
js = js + gas_settings.EXTRA_MEDIA.get('js', [])
|
|
ctx.update({
|
|
'base_template': self.base_template,
|
|
'home_url': self.get_home_url(),
|
|
'header_title': self.get_header_title(),
|
|
'title': self.get_title(),
|
|
'help_text': self.get_help_text(),
|
|
'breadcrumbs': self.get_breadcrumbs(),
|
|
'actions': self.get_actions(),
|
|
'gas_title': gas_settings.TITLE,
|
|
'logo_static_url': gas_settings.LOGO,
|
|
'css': css,
|
|
'js': js,
|
|
})
|
|
return ctx
|
|
|
|
|
|
class GASListView(GASMixin, ListView):
|
|
""" ListView, permite indicar un formulario para filtrar contenido. """
|
|
filter_form_class = None
|
|
|
|
def get_filter_form(self):
|
|
if self.filter_form_class is None:
|
|
return None
|
|
data = self.request.GET
|
|
return self.filter_form_class(data=data)
|
|
|
|
def filter_queryset(self, qs):
|
|
self.filter_form = self.get_filter_form()
|
|
if self.filter_form is not None and self.filter_form.is_valid():
|
|
return self.filter_form.filter(qs)
|
|
else:
|
|
return qs
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
return self.filter_queryset(qs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx.update({
|
|
'filter_form': self.filter_form,
|
|
})
|
|
return ctx
|
|
|
|
|
|
class GASCreateView(GASMixin, CreateView):
|
|
template_name = 'gas/base_form.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
is_popup = "_popup" in self.request.GET
|
|
ctx.update({
|
|
'is_popup': is_popup,
|
|
'form_id': 'main-form',
|
|
})
|
|
return ctx
|
|
|
|
def form_valid(self, form):
|
|
response = super().form_valid(form)
|
|
if '_popup' in self.request.POST:
|
|
return HttpResponse(
|
|
'<!DOCTYPE html><html><head><title></title></head><body>'
|
|
'<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' %
|
|
# escape() calls force_text.
|
|
(escape(self.object.pk), escapejs(self.object)))
|
|
else:
|
|
return response
|
|
|
|
|
|
class GASUpdateView(GASMixin, UpdateView):
|
|
""" Same as Django's UpdateView, defined only for completeness. """
|
|
template_name = 'gas/base_form.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx.update({
|
|
'form_id': 'main-form',
|
|
})
|
|
return ctx
|
|
|
|
|
|
class GASDeleteView(GASMixin, DeleteView):
|
|
template_name = "gas/delete_confirmation.html"
|
|
confirmation_text = _("Are you sure you want to delete {object}?")
|
|
deleted_text = _("{object} deleted.")
|
|
show_deleted_objects = True
|
|
|
|
def get_confirmation_text(self):
|
|
return self.confirmation_text.format(object=self.object)
|
|
|
|
def get_deleted_text(self):
|
|
return self.deleted_text.format(object=self.object)
|
|
|
|
def get_deleted_objects(self):
|
|
using = router.db_for_write(self.model)
|
|
collector = NestedObjects(using=using)
|
|
|
|
def format_callback(obj):
|
|
opts = obj._meta
|
|
return '%s: %s' % (capfirst(opts.verbose_name), obj)
|
|
|
|
collector.collect([self.object])
|
|
model_count = {model._meta.verbose_name_plural: len(objs) for model, objs in collector.model_objs.items()}
|
|
return collector.nested(format_callback), model_count
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
|
|
if self.show_deleted_objects:
|
|
deleted_objects, deleted_model_count = self.get_deleted_objects()
|
|
else:
|
|
deleted_objects = deleted_model_count = None
|
|
|
|
ctx.update({
|
|
'confirmation_text': self.get_confirmation_text(),
|
|
'cancel_url': self.get_success_url(),
|
|
'show_deleted_objects': self.show_deleted_objects,
|
|
'deleted_objects': deleted_objects,
|
|
'deleted_model_count': deleted_model_count,
|
|
})
|
|
return ctx
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
response = super().delete(request, *args, **kwargs)
|
|
messages.add_message(request, messages.SUCCESS, self.get_deleted_text())
|
|
return response
|