mirror of https://github.com/interlegis/sapl.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1241 lines
44 KiB
1241 lines
44 KiB
import logging
|
|
|
|
from braces.views import FormMessagesMixin
|
|
from compressor.utils.decorators import cached_property
|
|
from crispy_forms.bootstrap import FieldWithButtons, StrictButton
|
|
from crispy_forms.helper import FormHelper
|
|
from crispy_forms.layout import Field, Layout
|
|
from django import forms
|
|
from django.conf.urls import url
|
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import models
|
|
from django.http.response import Http404
|
|
from django.utils.decorators import classonlymethod
|
|
from django.utils.encoding import force_text
|
|
from django.utils.translation import string_concat
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
|
|
UpdateView)
|
|
from django.views.generic.base import ContextMixin
|
|
from django.views.generic.list import MultipleObjectMixin
|
|
|
|
from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display
|
|
from sapl.utils import normalize
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
|
|
'list', 'create', 'detail', 'update', 'delete'
|
|
|
|
# RP - Radical das permissões para "..."
|
|
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
|
|
'.list_', '.detail_', '.add_', '.change_', '.delete_',
|
|
|
|
|
|
def _form_invalid_message(msg):
|
|
return '%s %s' % (_('Formulário inválido.'), msg)
|
|
|
|
FORM_MESSAGES = {ACTION_CREATE: (_('Registro criado com sucesso!'),
|
|
_('O registro não foi criado.')),
|
|
ACTION_UPDATE: (_('Registro alterado com sucesso!'),
|
|
_('Suas alterações não foram salvas.')),
|
|
ACTION_DELETE: (_('Registro excluído com sucesso!'),
|
|
_('O registro não foi excluído.'))}
|
|
FORM_MESSAGES = {k: (a, _form_invalid_message(b))
|
|
for k, (a, b) in FORM_MESSAGES.items()}
|
|
|
|
|
|
def from_to(start, end):
|
|
return list(range(start, end + 1))
|
|
|
|
|
|
def make_pagination(index, num_pages):
|
|
'''Make a list of adjacent page ranges interspersed with "None"s
|
|
|
|
The list starts with [1, 2] and end with [num_pages-1, num_pages].
|
|
The list includes [index-1, index, index+1]
|
|
"None"s separate those ranges and mean ellipsis (...)
|
|
|
|
Example: [1, 2, None, 10, 11, 12, None, 29, 30]
|
|
'''
|
|
|
|
PAGINATION_LENGTH = 10
|
|
if num_pages <= PAGINATION_LENGTH:
|
|
return from_to(1, num_pages)
|
|
else:
|
|
if index - 1 <= 5:
|
|
tail = [num_pages - 1, num_pages]
|
|
head = from_to(1, PAGINATION_LENGTH - 3)
|
|
else:
|
|
if index + 1 >= num_pages - 3:
|
|
tail = from_to(index - 1, num_pages)
|
|
else:
|
|
tail = [index - 1, index, index + 1,
|
|
None, num_pages - 1, num_pages]
|
|
head = from_to(1, PAGINATION_LENGTH - len(tail) - 1)
|
|
return head + [None] + tail
|
|
|
|
"""
|
|
variáveis do crud:
|
|
help_path
|
|
container_field
|
|
container_field_set
|
|
is_m2m
|
|
model
|
|
model_set
|
|
form_search_class -> depende de o model relativo implementar SearchMixin
|
|
list_field_names
|
|
list_field_names_set -> lista reversa em details
|
|
permission_required -> este atributo ser vazio não nulo torna a view publ
|
|
layout_key_set
|
|
layout_key
|
|
ordered_list = False desativa os clicks e controles de ord da listagem
|
|
parent_field = parentesco reverso separado por '__'
|
|
namespace
|
|
"""
|
|
|
|
|
|
class SearchMixin(models.Model):
|
|
|
|
search = models.TextField(blank=True, default='')
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
def save(self, force_insert=False, force_update=False, using=None,
|
|
update_fields=None, auto_update_search=True):
|
|
|
|
if auto_update_search and hasattr(self, 'fields_search'):
|
|
search = ''
|
|
for str_field in self.fields_search:
|
|
fields = str_field.split('__')
|
|
if len(fields) == 1:
|
|
try:
|
|
search += str(getattr(self, str_field)) + ' '
|
|
except:
|
|
pass
|
|
else:
|
|
_self = self
|
|
for field in fields:
|
|
_self = getattr(_self, field)
|
|
search += str(_self) + ' '
|
|
self.search = search
|
|
self.search = normalize(self.search)
|
|
|
|
return super(SearchMixin, self).save(
|
|
force_insert=force_insert, force_update=force_update,
|
|
using=using, update_fields=update_fields)
|
|
|
|
|
|
class ListWithSearchForm(forms.Form):
|
|
q = forms.CharField(required=False, label='',
|
|
widget=forms.TextInput(
|
|
attrs={'type': 'search'}))
|
|
|
|
o = forms.CharField(required=False, label='',
|
|
widget=forms.HiddenInput())
|
|
|
|
class Meta:
|
|
fields = ['q', 'o']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(ListWithSearchForm, self).__init__(*args, **kwargs)
|
|
|
|
self.helper = FormHelper()
|
|
self.form_class = 'form-inline'
|
|
self.helper.form_method = 'GET'
|
|
self.helper.layout = Layout(
|
|
Field('o'),
|
|
FieldWithButtons(
|
|
Field('q',
|
|
placeholder=_('Filtrar Lista'),
|
|
css_class='input-lg'),
|
|
StrictButton(
|
|
_('Filtrar'), css_class='btn-default btn-lg',
|
|
type='submit'))
|
|
)
|
|
|
|
|
|
class PermissionRequiredForAppCrudMixin(PermissionRequiredMixin):
|
|
|
|
def has_permission(self):
|
|
apps = self.app_label
|
|
if isinstance(apps, str):
|
|
apps = apps,
|
|
# papp_label vazio dará acesso geral
|
|
for app in apps:
|
|
if not self.request.user.has_module_perms(app):
|
|
return False
|
|
return True
|
|
|
|
|
|
class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin):
|
|
|
|
def has_permission(self):
|
|
perms = self.get_permission_required()
|
|
# Torna a view pública se não possuir conteudo
|
|
# no atributo permission_required
|
|
return self.request.user.has_perms(perms) if len(perms) else True
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not self.has_permission():
|
|
return self.handle_no_permission()
|
|
|
|
if 'pk' in kwargs:
|
|
params = {'pk': kwargs['pk']}
|
|
|
|
if self.container_field:
|
|
params[self.container_field] = request.user.pk
|
|
|
|
if not self.model.objects.filter(**params).exists():
|
|
raise Http404()
|
|
|
|
return super(PermissionRequiredMixin, self).dispatch(
|
|
request, *args, **kwargs)
|
|
|
|
@cached_property
|
|
def container_field(self):
|
|
if hasattr(self, 'crud') and not hasattr(self.crud, 'container_field'):
|
|
self.crud.container_field = ''
|
|
if hasattr(self, 'crud'):
|
|
return self.crud.container_field
|
|
|
|
@cached_property
|
|
def container_field_set(self):
|
|
if hasattr(self, 'crud') and\
|
|
not hasattr(self.crud, 'container_field_set'):
|
|
self.crud.container_field_set = ''
|
|
if hasattr(self, 'crud'):
|
|
return self.crud.container_field_set
|
|
|
|
@cached_property
|
|
def is_contained(self):
|
|
return self.container_field_set or self.container_field
|
|
|
|
|
|
class CrudBaseMixin(CrispyLayoutFormMixin):
|
|
|
|
def __init__(self, **kwargs):
|
|
super(CrudBaseMixin, self).__init__(**kwargs)
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
self.app_label = obj.model._meta.app_label
|
|
self.model_name = obj.model._meta.model_name
|
|
|
|
if hasattr(obj, 'model_set') and obj.model_set:
|
|
self.app_label_set = getattr(
|
|
obj.model, obj.model_set).field.model._meta.app_label
|
|
self.model_name_set = getattr(
|
|
obj.model, obj.model_set).field.model._meta.model_name
|
|
|
|
if hasattr(self, 'permission_required') and self.permission_required:
|
|
if hasattr(obj, 'public'):
|
|
self.permission_required = list(
|
|
set(self.permission_required) - set(obj.public))
|
|
|
|
self.permission_required = tuple((
|
|
self.permission(pr) for pr in self.permission_required))
|
|
|
|
@classmethod
|
|
def url_name(cls, suffix):
|
|
return '%s_%s' % (cls.model._meta.model_name, suffix)
|
|
|
|
def url_name_set(self, suffix):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
return '%s_%s' % (getattr(obj.model, obj.model_set
|
|
).field.model._meta.model_name, suffix)
|
|
|
|
def permission(self, rad):
|
|
return '%s%s%s' % (self.app_label if rad.endswith('_') else '',
|
|
rad,
|
|
self.model_name if rad.endswith('_') else '')
|
|
|
|
def permission_set(self, rad):
|
|
return '%s%s%s' % (self.app_label_set if rad.endswith('_') else '',
|
|
rad,
|
|
self.model_name_set if rad.endswith('_') else '')
|
|
|
|
def resolve_url(self, suffix, args=None):
|
|
namespace = self.model._meta.app_config.name
|
|
return reverse('%s:%s' % (namespace, self.url_name(suffix)),
|
|
args=args)
|
|
|
|
def resolve_url_set(self, suffix, args=None):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
namespace = getattr(
|
|
obj.model, obj.model_set).field.model._meta.app_config.name
|
|
return reverse('%s:%s' % (namespace, self.url_name_set(suffix)),
|
|
args=args)
|
|
|
|
@property
|
|
def ordered_list(self):
|
|
return True
|
|
|
|
@property
|
|
def list_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.ListView.permission_required:
|
|
return self.resolve_url(ACTION_LIST)
|
|
else:
|
|
return self.resolve_url(
|
|
ACTION_LIST) if self.request.user.has_perm(
|
|
self.permission(RP_LIST)) else ''
|
|
|
|
@property
|
|
def create_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.CreateView.permission_required:
|
|
return self.resolve_url(ACTION_CREATE)
|
|
else:
|
|
return self.resolve_url(
|
|
ACTION_CREATE) if self.request.user.has_perm(
|
|
self.permission(RP_ADD)) else ''
|
|
|
|
@property
|
|
def detail_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.DetailView.permission_required:
|
|
return self.resolve_url(ACTION_DETAIL, args=(self.object.id,))
|
|
else:
|
|
return self.resolve_url(ACTION_DETAIL, args=(self.object.id,))\
|
|
if self.request.user.has_perm(
|
|
self.permission(RP_DETAIL)) else ''
|
|
|
|
@property
|
|
def update_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.UpdateView.permission_required:
|
|
return self.resolve_url(ACTION_UPDATE, args=(self.object.id,))
|
|
else:
|
|
return self.resolve_url(ACTION_UPDATE, args=(self.object.id,))\
|
|
if self.request.user.has_perm(
|
|
self.permission(RP_CHANGE)) else ''
|
|
|
|
@property
|
|
def delete_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.DeleteView.permission_required:
|
|
return self.resolve_url(ACTION_DELETE, args=(self.object.id,))
|
|
else:
|
|
return self.resolve_url(ACTION_DELETE, args=(self.object.id,))\
|
|
if self.request.user.has_perm(
|
|
self.permission(RP_DELETE)) else ''
|
|
|
|
def get_template_names(self):
|
|
names = super(CrudBaseMixin, self).get_template_names()
|
|
names.append("crud/%s.html" %
|
|
self.template_name_suffix.lstrip('_'))
|
|
return names
|
|
|
|
@property
|
|
def verbose_name_set(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
return getattr(obj.model, obj.model_set).field.model._meta.verbose_name
|
|
|
|
@property
|
|
def verbose_name(self):
|
|
return self.model._meta.verbose_name
|
|
|
|
@property
|
|
def verbose_name_plural(self):
|
|
return self.model._meta.verbose_name_plural
|
|
|
|
|
|
class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
|
|
permission_required = (RP_LIST, )
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^$'
|
|
paginate_by = 10
|
|
no_entries_msg = _('Nenhum registro encontrado.')
|
|
|
|
def get_rows(self, object_list):
|
|
return [self._as_row(obj) for obj in object_list]
|
|
|
|
def get_headers(self):
|
|
"""
|
|
Transforma o headers de fields de list_field_names
|
|
para junção de fields via tuplas.
|
|
list_field_names pode ser construido como
|
|
list_field_names=('nome', 'endereco', ('telefone', sexo'), 'dat_nasc')
|
|
ou ainda:
|
|
list_field_names = ['composicao__comissao__nome', 'cargo__nome', (
|
|
'composicao__periodo__data_inicio', 'composicao__periodo__data_fim')]
|
|
"""
|
|
r = []
|
|
for fieldname in self.list_field_names:
|
|
if not isinstance(fieldname, tuple):
|
|
fieldname = fieldname,
|
|
s = []
|
|
for fn in fieldname:
|
|
m = self.model
|
|
fn = fn.split('__')
|
|
for f in fn:
|
|
f = m._meta.get_field(f)
|
|
if hasattr(f, 'related_model') and f.related_model:
|
|
m = f.related_model
|
|
if m == self.model:
|
|
s.append(force_text(f.verbose_name))
|
|
else:
|
|
s.append(force_text(f.verbose_name))
|
|
s = ' / '.join(s)
|
|
r.append(s)
|
|
return r
|
|
|
|
def _as_row(self, obj):
|
|
r = []
|
|
for i, name in enumerate(self.list_field_names):
|
|
url = self.resolve_url(
|
|
ACTION_DETAIL, args=(obj.id,)) if i == 0 else None
|
|
|
|
"""Caso o crud list seja para uma relação ManyToManyField"""
|
|
if url and hasattr(self, 'crud') and\
|
|
hasattr(self.crud, 'is_m2m') and self.crud.is_m2m:
|
|
url = url + ('?pkk=' + self.kwargs['pk']
|
|
if 'pk' in self.kwargs else '')
|
|
|
|
if not isinstance(name, tuple):
|
|
name = name,
|
|
|
|
""" se elemento de list_field_name for uma tupla, constrói a
|
|
informação com ' - ' se os campos forem simples,
|
|
ou com <br> se for m2m """
|
|
if isinstance(name, tuple):
|
|
s = ''
|
|
for j, n in enumerate(name):
|
|
m = obj
|
|
n = n.split('__')
|
|
for f in n[:-1]:
|
|
m = getattr(m, f)
|
|
if not m:
|
|
break
|
|
if m:
|
|
ss = get_field_display(m, n[-1])[1]
|
|
ss = (
|
|
('<br>' if '<ul>' in ss else ' - ') + ss)\
|
|
if ss and j != 0 and s else ss
|
|
s += ss
|
|
r.append((s, url))
|
|
return r
|
|
|
|
def get_context_data(self, **kwargs):
|
|
""" Relevante se na implmentação do crud list, for informado
|
|
um formulário de pesquisa herdado ou o próprio ListWithSearchForm.
|
|
Só pode ser usado se o model relativo herdar de SearchMixin"""
|
|
if hasattr(self, 'form_search_class'):
|
|
q = str(self.request.GET.get('q'))\
|
|
if 'q' in self.request.GET else ''
|
|
|
|
o = self.request.GET['o'] if 'o' in self.request.GET else '1'
|
|
|
|
if 'form' not in kwargs:
|
|
initial = self.get_initial() if hasattr(
|
|
self, 'get_initial') else {}
|
|
initial.update({'q': q, 'o': o})
|
|
kwargs['form'] = self.form_search_class(
|
|
initial=initial)
|
|
|
|
count = self.object_list.count()
|
|
context = super().get_context_data(**kwargs)
|
|
context.setdefault('title', self.verbose_name_plural)
|
|
context['count'] = count
|
|
|
|
# pagination
|
|
if self.paginate_by:
|
|
page_obj = context['page_obj']
|
|
paginator = context['paginator']
|
|
context['page_range'] = make_pagination(
|
|
page_obj.number, paginator.num_pages)
|
|
|
|
# rows
|
|
object_list = context['object_list']
|
|
context['headers'] = self.get_headers()
|
|
context['rows'] = self.get_rows(object_list)
|
|
|
|
context['NO_ENTRIES_MSG'] = self.no_entries_msg
|
|
|
|
qr = self.request.GET.copy()
|
|
if 'page' in qr:
|
|
del qr['page']
|
|
context['filter_url'] = (
|
|
'&' + qr.urlencode()) if len(qr) > 0 else ''
|
|
|
|
if self.ordered_list:
|
|
if 'o' in qr:
|
|
del qr['o']
|
|
context['ordering_url'] = (
|
|
'&' + qr.urlencode()) if len(qr) > 0 else ''
|
|
return context
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
|
|
# form_search_class
|
|
# só pode ser usado em models que herdam de SearchMixin
|
|
if hasattr(self, 'form_search_class'):
|
|
request = self.request
|
|
if request.GET.get('q') is not None:
|
|
query = normalize(str(request.GET.get('q')))
|
|
|
|
query = query.split(' ')
|
|
if query:
|
|
q = models.Q()
|
|
for item in query:
|
|
if not item:
|
|
continue
|
|
q = q & models.Q(search__icontains=item)
|
|
|
|
if q:
|
|
queryset = queryset.filter(q)
|
|
|
|
if self.ordered_list:
|
|
list_field_names = self.list_field_names
|
|
o = '1'
|
|
desc = ''
|
|
if 'o' in self.request.GET:
|
|
o = self.request.GET['o']
|
|
desc = '-' if o.startswith('-') else ''
|
|
|
|
# Constroi a ordenação da listagem com base no que o usuário
|
|
# clicar
|
|
try:
|
|
fields_for_ordering = list_field_names[
|
|
(abs(int(o)) - 1) % len(list_field_names)]
|
|
|
|
if isinstance(fields_for_ordering, str):
|
|
fields_for_ordering = [fields_for_ordering, ]
|
|
|
|
ordering = ()
|
|
model = self.model
|
|
for fo in fields_for_ordering:
|
|
|
|
fm = None
|
|
try:
|
|
fm = model._meta.get_field(fo)
|
|
except:
|
|
pass
|
|
|
|
if fm and hasattr(fm, 'related_model')\
|
|
and fm.related_model:
|
|
rmo = fm.related_model._meta.ordering
|
|
if rmo:
|
|
rmo = rmo[0]
|
|
if not isinstance(rmo, str):
|
|
rmo = rmo[0]
|
|
fo = '%s__%s' % (fo, rmo)
|
|
|
|
fo = desc + fo
|
|
ordering += (fo,)
|
|
|
|
model = self.model
|
|
model_ordering = model._meta.ordering
|
|
if model_ordering:
|
|
if isinstance(model_ordering, str):
|
|
model_ordering = (model_ordering,)
|
|
for mo in model_ordering:
|
|
if mo not in ordering:
|
|
ordering = ordering + (mo, )
|
|
queryset = queryset.order_by(*ordering)
|
|
|
|
# print(ordering)
|
|
except Exception as e:
|
|
logger.error(string_concat(_(
|
|
'ERRO: construção da tupla de ordenação.'), str(e)))
|
|
|
|
# print(queryset.query)
|
|
if not self.request.user.is_authenticated():
|
|
return queryset
|
|
|
|
if self.container_field:
|
|
params = {}
|
|
params[self.container_field] = self.request.user.pk
|
|
queryset = queryset.filter(**params)
|
|
|
|
return queryset
|
|
|
|
|
|
class CrudCreateView(PermissionRequiredContainerCrudMixin,
|
|
FormMessagesMixin, CreateView):
|
|
permission_required = (RP_ADD, )
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^create$'
|
|
|
|
form_valid_message, form_invalid_message = FORM_MESSAGES[ACTION_CREATE]
|
|
|
|
@property
|
|
def cancel_url(self):
|
|
return self.list_url
|
|
|
|
def get_success_url(self):
|
|
return self.detail_url
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs.setdefault('title', _('Adicionar %(verbose_name)s') % {
|
|
'verbose_name': self.verbose_name})
|
|
return super(CrudCreateView, self).get_context_data(**kwargs)
|
|
|
|
def form_valid(self, form):
|
|
self.object = form.save(commit=False)
|
|
try:
|
|
self.object.owner = self.request.user
|
|
self.object.modifier = self.request.user
|
|
except:
|
|
pass
|
|
|
|
if self.container_field:
|
|
container = self.container_field.split('__')
|
|
|
|
if len(container) > 1:
|
|
container_model = getattr(
|
|
self.model, container[0]).field.related_model
|
|
|
|
params = {}
|
|
params['__'.join(
|
|
container[1:])] = self.request.user.pk
|
|
|
|
if 'pk' in self.kwargs:
|
|
params['pk'] = self.kwargs['pk']
|
|
|
|
container_data = container_model.objects.filter(
|
|
**params).first()
|
|
|
|
if not container_data:
|
|
raise Exception(
|
|
_('Não é permitido adicionar um registro '
|
|
'sem estar em um Container'))
|
|
|
|
if hasattr(self, 'crud') and\
|
|
hasattr(self.crud, 'is_m2m') and self.crud.is_m2m:
|
|
setattr(
|
|
self.object, container[1], getattr(
|
|
container_data, container[1]))
|
|
response = super().form_valid(form)
|
|
getattr(self.object, container[0]).add(container_data)
|
|
return response
|
|
else:
|
|
setattr(self.object, container[0], container_data)
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
class CrudDetailView(PermissionRequiredContainerCrudMixin,
|
|
DetailView, MultipleObjectMixin):
|
|
|
|
permission_required = (RP_DETAIL, )
|
|
no_entries_msg = _('Nenhum registro Associado.')
|
|
paginate_by = 10
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^(?P<pk>\d+)$'
|
|
|
|
def get_rows(self, object_list):
|
|
return [self._as_row(obj) for obj in object_list]
|
|
|
|
def get_headers(self):
|
|
if not self.object_list:
|
|
return []
|
|
try:
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
return [
|
|
(getattr(
|
|
self.object, obj.model_set).model._meta.get_field(
|
|
fieldname).verbose_name
|
|
if hasattr(self.object, fieldname) else
|
|
getattr(
|
|
self.object, obj.model_set).model._meta.get_field(
|
|
fieldname).related_model._meta.verbose_name_plural)
|
|
for fieldname in self.list_field_names_set]
|
|
except:
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
return [getattr(
|
|
self.object,
|
|
obj.model_set).model._meta.verbose_name_plural]
|
|
|
|
def url_model_set_name(self, suffix):
|
|
return '%s_%s' % (
|
|
getattr(self.object,
|
|
self.crud.model_set).model._meta.model_name,
|
|
suffix)
|
|
|
|
def resolve_model_set_url(self, suffix, args=None):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
namespace = getattr(
|
|
self.object, obj.model_set).model._meta.app_config.name
|
|
return reverse('%s:%s' % (
|
|
namespace, self.url_model_set_name(suffix)),
|
|
args=args)
|
|
|
|
def _as_row(self, obj):
|
|
try:
|
|
return [(
|
|
get_field_display(obj, name)[1],
|
|
self.resolve_model_set_url(ACTION_DETAIL, args=(obj.id,))
|
|
if i == 0 else None)
|
|
for i, name in enumerate(self.list_field_names_set)]
|
|
except:
|
|
return [(
|
|
getattr(obj, name),
|
|
self.resolve_model_set_url(ACTION_DETAIL, args=(obj.id,))
|
|
if i == 0 else None)
|
|
for i, name in enumerate(self.list_field_names_set)]
|
|
|
|
def get_object(self, queryset=None):
|
|
if hasattr(self, 'object'):
|
|
return self.object
|
|
return DetailView.get_object(self, queryset=queryset)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
try:
|
|
self.object = self.model.objects.get(pk=kwargs.get('pk'))
|
|
except:
|
|
raise Http404
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if hasattr(obj, 'model_set') and obj.model_set:
|
|
self.object_list = self.get_queryset()
|
|
context = self.get_context_data(object=self.object)
|
|
return self.render_to_response(context)
|
|
|
|
def get_queryset(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if hasattr(obj, 'model_set') and obj.model_set:
|
|
queryset = getattr(self.object, obj.model_set).all()
|
|
else:
|
|
queryset = super().get_queryset()
|
|
|
|
if not self.request.user.is_authenticated():
|
|
return queryset
|
|
|
|
if self.container_field_set:
|
|
params = {}
|
|
params[self.container_field_set] = self.request.user.pk
|
|
return queryset.filter(**params)
|
|
|
|
return queryset
|
|
|
|
def get_context_data(self, **kwargs):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if hasattr(obj, 'model_set') and obj.model_set:
|
|
count = self.object_list.count()
|
|
context = MultipleObjectMixin.get_context_data(self, **kwargs)
|
|
context['count'] = count
|
|
if self.paginate_by:
|
|
page_obj = context['page_obj']
|
|
paginator = context['paginator']
|
|
context['page_range'] = make_pagination(
|
|
page_obj.number, paginator.num_pages)
|
|
|
|
# rows
|
|
object_list = context['object_list']
|
|
context['headers'] = self.get_headers()
|
|
context['rows'] = self.get_rows(object_list)
|
|
|
|
context['NO_ENTRIES_MSG'] = self.no_entries_msg
|
|
else:
|
|
context = ContextMixin.get_context_data(self, **kwargs)
|
|
if self.object:
|
|
context['object'] = self.object
|
|
context_object_name = self.get_context_object_name(
|
|
self.object)
|
|
if context_object_name:
|
|
context[context_object_name] = self.object
|
|
context.update(kwargs)
|
|
|
|
return context
|
|
|
|
@property
|
|
def model_set_verbose_name(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
return getattr(
|
|
self.object,
|
|
obj.model_set).model._meta.verbose_name
|
|
|
|
@property
|
|
def model_set_verbose_name_plural(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
return getattr(
|
|
self.object,
|
|
obj.model_set).model._meta.verbose_name_plural
|
|
|
|
|
|
class CrudUpdateView(PermissionRequiredContainerCrudMixin,
|
|
FormMessagesMixin, UpdateView):
|
|
permission_required = (RP_CHANGE, )
|
|
|
|
def form_valid(self, form):
|
|
self.object = form.save(commit=False)
|
|
try:
|
|
self.object.modifier = self.request.user
|
|
except:
|
|
pass
|
|
|
|
return super().form_valid(form)
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^(?P<pk>\d+)/edit$'
|
|
|
|
form_valid_message, form_invalid_message = FORM_MESSAGES[ACTION_UPDATE]
|
|
|
|
@property
|
|
def cancel_url(self):
|
|
return self.detail_url
|
|
|
|
def get_success_url(self):
|
|
return self.detail_url
|
|
|
|
|
|
class CrudDeleteView(PermissionRequiredContainerCrudMixin,
|
|
FormMessagesMixin, DeleteView):
|
|
permission_required = (RP_DELETE, )
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^(?P<pk>\d+)/delete$'
|
|
|
|
form_valid_message, form_invalid_message = FORM_MESSAGES[ACTION_DELETE]
|
|
|
|
@property
|
|
def cancel_url(self):
|
|
return self.detail_url
|
|
|
|
def get_success_url(self):
|
|
return self.list_url
|
|
|
|
|
|
class Crud:
|
|
BaseMixin = CrudBaseMixin
|
|
ListView = CrudListView
|
|
CreateView = CrudCreateView
|
|
DetailView = CrudDetailView
|
|
UpdateView = CrudUpdateView
|
|
DeleteView = CrudDeleteView
|
|
help_path = ''
|
|
|
|
class PublicMixin:
|
|
permission_required = []
|
|
|
|
@classonlymethod
|
|
def get_urls(cls):
|
|
|
|
def _add_base(view):
|
|
if view:
|
|
class CrudViewWithBase(cls.BaseMixin, view):
|
|
model = cls.model
|
|
help_path = cls.help_path
|
|
crud = cls
|
|
CrudViewWithBase.__name__ = view.__name__
|
|
return CrudViewWithBase
|
|
|
|
CrudListView = _add_base(cls.ListView)
|
|
CrudCreateView = _add_base(cls.CreateView)
|
|
CrudDetailView = _add_base(cls.DetailView)
|
|
CrudUpdateView = _add_base(cls.UpdateView)
|
|
CrudDeleteView = _add_base(cls.DeleteView)
|
|
|
|
cruds_base = [
|
|
(CrudListView.get_url_regex()
|
|
if CrudListView else None, CrudListView, ACTION_LIST),
|
|
(CrudCreateView.get_url_regex()
|
|
if CrudCreateView else None, CrudCreateView, ACTION_CREATE),
|
|
(CrudDetailView.get_url_regex()
|
|
if CrudDetailView else None, CrudDetailView, ACTION_DETAIL),
|
|
(CrudUpdateView.get_url_regex()
|
|
if CrudUpdateView else None, CrudUpdateView, ACTION_UPDATE),
|
|
(CrudDeleteView.get_url_regex()
|
|
if CrudDeleteView else None, CrudDeleteView, ACTION_DELETE)]
|
|
|
|
cruds = []
|
|
for crud in cruds_base:
|
|
if crud[0]:
|
|
cruds.append(crud)
|
|
|
|
return [url(regex, view.as_view(), name=view.url_name(suffix))
|
|
for regex, view, suffix in cruds]
|
|
|
|
@classonlymethod
|
|
def build(cls, _model, _help_path, _model_set=None, list_field_names=[]):
|
|
|
|
def create_class(_list_field_names):
|
|
class ModelCrud(cls):
|
|
model = _model
|
|
model_set = _model_set
|
|
help_path = _help_path
|
|
list_field_names = _list_field_names
|
|
return ModelCrud
|
|
|
|
ModelCrud = create_class(list_field_names)
|
|
ModelCrud.__name__ = '%sCrud' % _model.__name__
|
|
return ModelCrud
|
|
|
|
|
|
class CrudAux(Crud):
|
|
|
|
class BaseMixin(Crud.BaseMixin):
|
|
permission_required = ('base.view_tabelas_auxiliares',)
|
|
subnav_template_name = None
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['subnav_template_name'] = self.subnav_template_name
|
|
return context
|
|
|
|
@classonlymethod
|
|
def build(cls, _model, _help_path, _model_set=None, list_field_names=[]):
|
|
|
|
ModelCrud = Crud.build(
|
|
_model, _help_path, _model_set, list_field_names)
|
|
|
|
class ModelCrudAux(ModelCrud):
|
|
BaseMixin = CrudAux.BaseMixin
|
|
|
|
return ModelCrudAux
|
|
|
|
|
|
class MasterDetailCrud(Crud):
|
|
is_m2m = False
|
|
|
|
class BaseMixin(Crud.BaseMixin):
|
|
|
|
@property
|
|
def list_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.ListView:
|
|
return ''
|
|
return self.resolve_url(ACTION_LIST, args=(self.kwargs['pk'],))\
|
|
if self.request.user.has_perm(self.permission(RP_LIST)) else ''
|
|
|
|
@property
|
|
def create_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.CreateView:
|
|
return ''
|
|
return self.resolve_url(ACTION_CREATE, args=(self.kwargs['pk'],))\
|
|
if self.request.user.has_perm(self.permission(RP_ADD)) else ''
|
|
|
|
@property
|
|
def detail_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.DetailView:
|
|
return ''
|
|
pkk = self.request.GET['pkk'] if 'pkk' in self.request.GET else ''
|
|
return (super().detail_url + (('?pkk=' + pkk) if pkk else ''))\
|
|
if self.request.user.has_perm(
|
|
self.permission(RP_DETAIL)) else ''
|
|
|
|
@property
|
|
def update_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.UpdateView:
|
|
return ''
|
|
pkk = self.request.GET['pkk'] if 'pkk' in self.request.GET else ''
|
|
return (super().update_url + (('?pkk=' + pkk) if pkk else ''))\
|
|
if self.request.user.has_perm(
|
|
self.permission(RP_CHANGE)) else ''
|
|
|
|
@property
|
|
def delete_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.DeleteView:
|
|
return ''
|
|
return super().delete_url\
|
|
if self.request.user.has_perm(
|
|
self.permission(RP_DELETE)) else ''
|
|
|
|
def get_context_data(self, **kwargs):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
self.object = getattr(self, 'object', None)
|
|
parent_object = None
|
|
if self.object:
|
|
if '__' in obj.parent_field:
|
|
fields = obj.parent_field.split('__')
|
|
parent_object = self.object
|
|
for field in fields:
|
|
parent_object = getattr(parent_object, field)
|
|
else:
|
|
parent_object = getattr(self.object, obj.parent_field)
|
|
if not isinstance(parent_object, models.Model):
|
|
if parent_object.count() > 1:
|
|
if 'pkk' not in self.request.GET:
|
|
raise Http404
|
|
root_pk = self.request.GET['pkk']
|
|
parent_object = parent_object.filter(id=root_pk)
|
|
|
|
parent_object = parent_object.first()
|
|
|
|
if not parent_object:
|
|
raise Http404
|
|
root_pk = parent_object.pk
|
|
else:
|
|
root_pk = self.kwargs['pk'] # in list and create
|
|
kwargs.setdefault('root_pk', root_pk)
|
|
context = super(CrudBaseMixin, self).get_context_data(**kwargs)
|
|
|
|
if parent_object:
|
|
context['title'] = '%s <small>(%s)</small>' % (
|
|
self.object, parent_object)
|
|
|
|
return context
|
|
|
|
class ListView(Crud.ListView):
|
|
permission_required = RP_LIST,
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^(?P<pk>\d+)/%s$' % cls.model._meta.model_name
|
|
|
|
def get_context_data(self, **kwargs):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
context = CrudListView.get_context_data(self, **kwargs)
|
|
|
|
parent_model = None
|
|
if '__' in obj.parent_field:
|
|
fields = obj.parent_field.split('__')
|
|
parent_model = self.model
|
|
for field in fields:
|
|
parent_model = getattr(
|
|
parent_model, field).field.related_model
|
|
else:
|
|
parent_model = getattr(
|
|
self.model, obj.parent_field).field.related_model
|
|
|
|
params = {'pk': kwargs['root_pk']}
|
|
|
|
if self.container_field:
|
|
container = self.container_field.split('__')
|
|
if len(container) > 1:
|
|
params['__'.join(container[1:])] = self.request.user.pk
|
|
|
|
try:
|
|
parent_object = parent_model.objects.get(**params)
|
|
except:
|
|
raise Http404()
|
|
|
|
context[
|
|
'title'] = '%s <small>(%s)</small>' % (
|
|
context['title'], parent_object)
|
|
return context
|
|
|
|
def get_queryset(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
qs = super().get_queryset()
|
|
|
|
kwargs = {obj.parent_field: self.kwargs['pk']}
|
|
|
|
"""if self.container_field:
|
|
kwargs[self.container_field] = self.request.user.pk"""
|
|
|
|
return qs.filter(**kwargs)
|
|
|
|
class CreateView(Crud.CreateView):
|
|
permission_required = RP_ADD,
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
return PermissionRequiredMixin.dispatch(
|
|
self, request, *args, **kwargs)
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^(?P<pk>\d+)/%s/create$' % cls.model._meta.model_name
|
|
|
|
def get_form(self, form_class=None):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
form = super(MasterDetailCrud.CreateView, self).get_form(
|
|
self.form_class)
|
|
if not obj.is_m2m:
|
|
parent_field = obj.parent_field.split('__')[0]
|
|
field = self.model._meta.get_field(parent_field)
|
|
parent = field.related_model.objects.get(pk=self.kwargs['pk'])
|
|
setattr(form.instance, parent_field, parent)
|
|
return form
|
|
|
|
def get_context_data(self, **kwargs):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
context = Crud.CreateView.get_context_data(
|
|
self, **kwargs)
|
|
|
|
params = {'pk': self.kwargs['pk']}
|
|
if self.container_field:
|
|
parent_model = getattr(
|
|
self.model, obj.parent_field).field.related_model
|
|
|
|
container = self.container_field.split('__')
|
|
if len(container) > 1:
|
|
params['__'.join(container[1:])] = self.request.user.pk
|
|
|
|
try:
|
|
parent = parent_model.objects.get(**params)
|
|
except:
|
|
raise Http404()
|
|
else:
|
|
parent_field = obj.parent_field.split('__')[0]
|
|
|
|
field = self.model._meta.get_field(parent_field)
|
|
parent = field.related_model.objects.get(**params)
|
|
if parent:
|
|
context['title'] = '%s <small>(%s)</small>' % (
|
|
context['title'], parent)
|
|
|
|
return context
|
|
|
|
class UpdateView(Crud.UpdateView):
|
|
permission_required = RP_CHANGE,
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^%s/(?P<pk>\d+)/edit$' % cls.model._meta.model_name
|
|
|
|
class DeleteView(Crud.DeleteView):
|
|
permission_required = RP_DELETE,
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^%s/(?P<pk>\d+)/delete$' % cls.model._meta.model_name
|
|
|
|
def get_success_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
parent_object = getattr(
|
|
self.get_object(), obj.parent_field)
|
|
if not isinstance(parent_object, models.Model):
|
|
if parent_object.count() > 1:
|
|
if 'pkk' not in self.request.GET:
|
|
raise Http404
|
|
root_pk = self.request.GET['pkk']
|
|
parent_object = parent_object.filter(id=root_pk)
|
|
|
|
parent_object = parent_object.first()
|
|
|
|
if not parent_object:
|
|
raise Http404
|
|
root_pk = parent_object.pk
|
|
|
|
pk = root_pk
|
|
return self.resolve_url(ACTION_LIST, args=(pk,))
|
|
|
|
class DetailView(Crud.DetailView):
|
|
permission_required = RP_DETAIL,
|
|
template_name = 'crud/detail_detail.html'
|
|
|
|
@classmethod
|
|
def get_url_regex(cls):
|
|
return r'^%s/(?P<pk>\d+)$' % cls.model._meta.model_name
|
|
|
|
@property
|
|
def detail_list_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.ListView.permission_required or\
|
|
self.request.user.has_perm(self.permission(RP_LIST)):
|
|
if '__' in obj.parent_field:
|
|
fields = obj.parent_field.split('__')
|
|
parent_object = self.object
|
|
for field in fields:
|
|
parent_object = getattr(parent_object, field)
|
|
else:
|
|
parent_object = getattr(self.object, obj.parent_field)
|
|
|
|
if not isinstance(parent_object, models.Model):
|
|
if parent_object.count() > 1:
|
|
if 'pkk' not in self.request.GET:
|
|
raise Http404
|
|
root_pk = self.request.GET['pkk']
|
|
parent_object = parent_object.filter(id=root_pk)
|
|
|
|
parent_object = parent_object.first()
|
|
|
|
if not parent_object:
|
|
raise Http404
|
|
root_pk = parent_object.pk
|
|
|
|
pk = root_pk
|
|
return self.resolve_url(ACTION_LIST, args=(pk,))
|
|
else:
|
|
return ''
|
|
|
|
@property
|
|
def detail_create_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if not obj.CreateView:
|
|
return ''
|
|
|
|
if self.request.user.has_perm(self.permission(RP_ADD)):
|
|
parent_field = obj.parent_field.split('__')[0]
|
|
parent_object = getattr(self.object, parent_field)
|
|
|
|
if not isinstance(parent_object, models.Model):
|
|
if parent_object.count() > 1:
|
|
if 'pkk' not in self.request.GET:
|
|
raise Http404
|
|
root_pk = self.request.GET['pkk']
|
|
parent_object = parent_object.filter(id=root_pk)
|
|
|
|
parent_object = parent_object.first()
|
|
|
|
if not parent_object:
|
|
raise Http404
|
|
root_pk = parent_object.pk
|
|
pk = root_pk
|
|
return self.resolve_url(ACTION_CREATE, args=(pk,))
|
|
else:
|
|
return ''
|
|
|
|
@property
|
|
def detail_set_create_url(self):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if hasattr(obj, 'model_set') and obj.model_set\
|
|
and self.request.user.has_perm(
|
|
self.permission_set(RP_ADD)):
|
|
root_pk = self.object .pk
|
|
pk = root_pk
|
|
return self.resolve_url_set(ACTION_CREATE, args=(pk,))
|
|
else:
|
|
return ''
|
|
|
|
@property
|
|
def detail_root_detail_url(self):
|
|
"""
|
|
Implementar retorno para o parent_field imediato no caso de
|
|
edição em cascata de MasterDetailDetail...
|
|
"""
|
|
return ''
|
|
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
if hasattr(obj, 'parent_field'):
|
|
# parent_field = obj.parent_field.split('__')[0]
|
|
|
|
root_pk = self.object .pk
|
|
pk = root_pk
|
|
return self.resolve_url(ACTION_DELETE, args=(pk,))
|
|
else:
|
|
return ''
|
|
|
|
@classonlymethod
|
|
def build(cls, model, parent_field, help_path,
|
|
_model_set=None, list_field_names=[]):
|
|
crud = super(MasterDetailCrud, cls).build(
|
|
model, help_path, _model_set=_model_set,
|
|
list_field_names=list_field_names)
|
|
crud.parent_field = parent_field
|
|
return crud
|
|
|
|
|
|
class CrudBaseForListAndDetailExternalAppView(MasterDetailCrud):
|
|
CreateView, UpdateView, DeleteView = None, None, None
|
|
|
|
class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin):
|
|
|
|
@classmethod
|
|
def url_name(cls, suffix):
|
|
return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
|
|
|
|
def resolve_url(self, suffix, args=None):
|
|
obj = self.crud if hasattr(self, 'crud') else self
|
|
|
|
""" namespace deve ser redirecionado para app local pois
|
|
o models colocados nos cruds que herdam este Crud são de outras app
|
|
"""
|
|
return reverse('%s:%s' % (obj.namespace, self.url_name(suffix)),
|
|
args=args)
|
|
|