|
|
@ -2,16 +2,13 @@ from braces.views import FormMessagesMixin |
|
|
|
from crispy_forms.helper import FormHelper |
|
|
|
from django import forms |
|
|
|
from django.conf.urls import url |
|
|
|
from django.core.urlresolvers import reverse, reverse_lazy |
|
|
|
from django.utils.functional import cached_property |
|
|
|
from django.core.urlresolvers import reverse |
|
|
|
from django.utils.translation import ugettext_lazy as _ |
|
|
|
from django.views.generic import (CreateView, DeleteView, DetailView, ListView, |
|
|
|
UpdateView) |
|
|
|
|
|
|
|
from sapl.layout import SaplFormLayout |
|
|
|
|
|
|
|
NO_ENTRIES_MSG = _('Não existem registros') |
|
|
|
|
|
|
|
|
|
|
|
def from_to(start, end): |
|
|
|
return list(range(start, end + 1)) |
|
|
@ -51,199 +48,247 @@ def get_field_display(obj, fieldname): |
|
|
|
value = getattr(obj, 'get_%s_display' % fieldname)() |
|
|
|
else: |
|
|
|
value = getattr(obj, fieldname) |
|
|
|
|
|
|
|
if value is None: |
|
|
|
display = '' |
|
|
|
elif 'date' in str(type(value)): |
|
|
|
display = value.strftime("%d/%m/%Y") |
|
|
|
display = value.strftime("%d/%m/%Y") # TODO: localize |
|
|
|
elif 'bool' in str(type(value)): |
|
|
|
display = 'Sim' if value else 'Não' |
|
|
|
else: |
|
|
|
display = str(value) |
|
|
|
|
|
|
|
return verbose_name, display |
|
|
|
|
|
|
|
|
|
|
|
class CrispyLayoutFormMixin(object): |
|
|
|
|
|
|
|
def get_form_class(self): |
|
|
|
|
|
|
|
layout = self.layout |
|
|
|
|
|
|
|
class CrispyForm(forms.ModelForm): |
|
|
|
|
|
|
|
class Meta: |
|
|
|
model = self.model |
|
|
|
exclude = [] |
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
super(CrispyForm, self).__init__(*args, **kwargs) |
|
|
|
self.helper = FormHelper() |
|
|
|
self.helper.layout = SaplFormLayout(*layout) |
|
|
|
|
|
|
|
return CrispyForm |
|
|
|
|
|
|
|
@property |
|
|
|
def list_field_names(self): |
|
|
|
'''The list of field names to display on table |
|
|
|
|
|
|
|
This base implementation returns the field names |
|
|
|
in the first fieldset of the layout. |
|
|
|
''' |
|
|
|
rows = self.layout[0][1:] |
|
|
|
return [fieldname for row in rows for fieldname, __ in row] |
|
|
|
|
|
|
|
def get_column(self, fieldname, span): |
|
|
|
obj = self.get_object() |
|
|
|
verbose_name, text = get_field_display(obj, fieldname) |
|
|
|
return { |
|
|
|
'id': fieldname, |
|
|
|
'span': span, |
|
|
|
'verbose_name': verbose_name, |
|
|
|
'text': text, |
|
|
|
} |
|
|
|
|
|
|
|
@property |
|
|
|
def fieldsets(self): |
|
|
|
return [ |
|
|
|
{'legend': legend, |
|
|
|
'rows': [[self.get_column(fieldname, span) |
|
|
|
for fieldname, span in row] |
|
|
|
for row in rows] |
|
|
|
} for legend, *rows in self.layout] |
|
|
|
|
|
|
|
|
|
|
|
class BaseCrudMixin(CrispyLayoutFormMixin): |
|
|
|
|
|
|
|
@property |
|
|
|
def namespace(self): |
|
|
|
return self.model._meta.model_name |
|
|
|
|
|
|
|
def resolve_url(self, url_name, args=None): |
|
|
|
return reverse('%s:%s' % (self.namespace, url_name), args=args) |
|
|
|
|
|
|
|
@property |
|
|
|
def list_url(self): |
|
|
|
return self.resolve_url('list') |
|
|
|
|
|
|
|
@property |
|
|
|
def create_url(self): |
|
|
|
return self.resolve_url('create') |
|
|
|
|
|
|
|
@property |
|
|
|
def detail_url(self): |
|
|
|
return self.resolve_url('detail', args=(self.object.id,)) |
|
|
|
|
|
|
|
@property |
|
|
|
def update_url(self): |
|
|
|
return self.resolve_url('update', args=(self.object.id,)) |
|
|
|
|
|
|
|
@property |
|
|
|
def delete_url(self): |
|
|
|
return self.resolve_url('delete', args=(self.object.id,)) |
|
|
|
|
|
|
|
def get_template_names(self): |
|
|
|
names = super(BaseCrudMixin, self).get_template_names() |
|
|
|
names.append("crud/%s.html" % |
|
|
|
self.template_name_suffix.lstrip('_')) |
|
|
|
return names |
|
|
|
|
|
|
|
@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 CrudListMixin(): |
|
|
|
|
|
|
|
paginate_by = 10 |
|
|
|
no_entries_msg = _('Nenhum registro encontrado.') |
|
|
|
|
|
|
|
@property |
|
|
|
def title(self): |
|
|
|
return self.verbose_name_plural |
|
|
|
|
|
|
|
def _as_row(self, obj): |
|
|
|
return [ |
|
|
|
(get_field_display(obj, name)[1], obj.pk if i == 0 else None) |
|
|
|
for i, name in enumerate(self.list_field_names)] |
|
|
|
|
|
|
|
def get_context_data(self, **kwargs): |
|
|
|
context = super(CrudListMixin, self).get_context_data(**kwargs) |
|
|
|
|
|
|
|
# pagination |
|
|
|
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['rows'] = [self._as_row(obj) for obj in object_list] |
|
|
|
|
|
|
|
context['headers'] = [ |
|
|
|
self.model._meta.get_field(fieldname).verbose_name |
|
|
|
for fieldname in self.list_field_names] |
|
|
|
context['NO_ENTRIES_MSG'] = self.no_entries_msg |
|
|
|
|
|
|
|
return context |
|
|
|
|
|
|
|
|
|
|
|
def make_form_invalid_message(msg): |
|
|
|
return '%s %s' % (_('Formulário inválido.'), msg) |
|
|
|
|
|
|
|
|
|
|
|
class CrudCreateMixin(FormMessagesMixin): |
|
|
|
|
|
|
|
form_valid_message = _('Registro criado com sucesso!') |
|
|
|
form_invalid_message = make_form_invalid_message( |
|
|
|
_('O registro não foi criado.')) |
|
|
|
|
|
|
|
@property |
|
|
|
def title(self): |
|
|
|
return _('Adicionar %(verbose_name)s') % { |
|
|
|
'verbose_name': self.verbose_name} |
|
|
|
|
|
|
|
@property |
|
|
|
def cancel_url(self): |
|
|
|
return self.list_url |
|
|
|
|
|
|
|
def get_success_url(self): |
|
|
|
return self.detail_url |
|
|
|
|
|
|
|
|
|
|
|
class CrudDetailMixin(): |
|
|
|
|
|
|
|
@property |
|
|
|
def title(self): |
|
|
|
# TODO: can we just use view.object in the templates???? |
|
|
|
return str(self.object) |
|
|
|
|
|
|
|
|
|
|
|
class CrudUpdateMixin(FormMessagesMixin): |
|
|
|
|
|
|
|
form_valid_message = _('Registro alterado com sucesso!') |
|
|
|
form_invalid_message = make_form_invalid_message( |
|
|
|
_('Suas alterações não foram salvas.')) |
|
|
|
|
|
|
|
@property |
|
|
|
def title(self): |
|
|
|
return str(self.object) |
|
|
|
|
|
|
|
@property |
|
|
|
def cancel_url(self): |
|
|
|
return self.detail_url |
|
|
|
|
|
|
|
def get_success_url(self): |
|
|
|
return self.detail_url |
|
|
|
|
|
|
|
|
|
|
|
class CrudDeleteMixin(FormMessagesMixin): |
|
|
|
|
|
|
|
form_valid_message = _('Registro excluído com sucesso!') |
|
|
|
form_invalid_message = make_form_invalid_message( |
|
|
|
_('O registro não foi excluído.')) |
|
|
|
|
|
|
|
def get_success_url(self): |
|
|
|
return self.list_url |
|
|
|
|
|
|
|
|
|
|
|
class Crud(object): |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def build_crud(model, help_path, layout): |
|
|
|
crud = Crud() |
|
|
|
crud.model = model |
|
|
|
crud.help_path = help_path |
|
|
|
crud.namespace = model._meta.model_name |
|
|
|
|
|
|
|
class CrispyForm(forms.ModelForm): |
|
|
|
|
|
|
|
class Meta: |
|
|
|
model = crud.model |
|
|
|
exclude = [] |
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
super(CrispyForm, self).__init__(*args, **kwargs) |
|
|
|
self.helper = FormHelper() |
|
|
|
self.helper.layout = SaplFormLayout(*layout) |
|
|
|
|
|
|
|
crud.model_form = CrispyForm |
|
|
|
|
|
|
|
def in_namespace(url_name): |
|
|
|
return '%s:%s' % (crud.namespace, url_name) |
|
|
|
|
|
|
|
def make_form_invalid_message(msg): |
|
|
|
return '%s %s' % (_('Formulário inválido.'), msg) |
|
|
|
|
|
|
|
class BaseMixin(object): |
|
|
|
model = crud.model |
|
|
|
|
|
|
|
verbose_name = crud.model._meta.verbose_name |
|
|
|
verbose_name_plural = crud.model._meta.verbose_name_plural |
|
|
|
|
|
|
|
list_url = reverse_lazy(in_namespace('list')) |
|
|
|
create_url = reverse_lazy(in_namespace('create')) |
|
|
|
help_path = crud.help_path # FIXME |
|
|
|
|
|
|
|
def get_url_for_this_object(self, url_name): |
|
|
|
return reverse(in_namespace(url_name), args=(self.object.id,)) |
|
|
|
|
|
|
|
@property |
|
|
|
def detail_url(self): |
|
|
|
return self.get_url_for_this_object('detail') |
|
|
|
|
|
|
|
@property |
|
|
|
def update_url(self): |
|
|
|
return self.get_url_for_this_object('update') |
|
|
|
|
|
|
|
@property |
|
|
|
def delete_url(self): |
|
|
|
return self.get_url_for_this_object('delete') |
|
|
|
|
|
|
|
def get_template_names(self): |
|
|
|
names = super(BaseMixin, self).get_template_names() |
|
|
|
names.append("crud/%s.html" % |
|
|
|
self.template_name_suffix.lstrip('_')) |
|
|
|
return names |
|
|
|
|
|
|
|
class CrudListView(BaseMixin, ListView): |
|
|
|
title = BaseMixin.verbose_name_plural |
|
|
|
paginate_by = 10 |
|
|
|
no_entries_msg = NO_ENTRIES_MSG |
|
|
|
|
|
|
|
@cached_property |
|
|
|
def field_names(self): |
|
|
|
'''The list of field names to display on table |
|
|
|
|
|
|
|
This base implementation returns the field names |
|
|
|
in the first fieldset of the layout. |
|
|
|
''' |
|
|
|
rows = layout[0][1:] |
|
|
|
return [fieldname for row in rows for fieldname, __ in row] |
|
|
|
|
|
|
|
def get_rows(self, object_list): |
|
|
|
return [[(get_field_display(obj, name)[1], |
|
|
|
obj.pk if i == 0 else None) |
|
|
|
for i, name in enumerate(self.field_names)] |
|
|
|
for obj in object_list |
|
|
|
] |
|
|
|
|
|
|
|
def get_context_data(self, **kwargs): |
|
|
|
context = super(CrudListView, self).get_context_data(**kwargs) |
|
|
|
paginator = context['paginator'] |
|
|
|
page_obj = context['page_obj'] |
|
|
|
context['page_range'] = make_pagination( |
|
|
|
page_obj.number, paginator.num_pages) |
|
|
|
object_list = context['object_list'] |
|
|
|
context['headers'] = [ |
|
|
|
self.model._meta.get_field(fieldname).verbose_name |
|
|
|
for fieldname in self.field_names] |
|
|
|
context['rows'] = self.get_rows(object_list) |
|
|
|
context['NO_ENTRIES_MSG'] = NO_ENTRIES_MSG |
|
|
|
return context |
|
|
|
|
|
|
|
class CrudCreateView(BaseMixin, FormMessagesMixin, CreateView): |
|
|
|
form_class = crud.model_form |
|
|
|
title = _('Adicionar %(verbose_name)s') % { |
|
|
|
'verbose_name': BaseMixin.verbose_name} |
|
|
|
form_valid_message = _('Registro criado com sucesso!') |
|
|
|
form_invalid_message = make_form_invalid_message( |
|
|
|
_('O registro não foi criado.')) |
|
|
|
cancel_url = BaseMixin.list_url |
|
|
|
|
|
|
|
def form_invalid(self, form): |
|
|
|
""" |
|
|
|
If the form is invalid, re-render the context data with the |
|
|
|
data-filled form and errors. |
|
|
|
""" |
|
|
|
return self.render_to_response(self.get_context_data(form=form)) |
|
|
|
|
|
|
|
def get_success_url(self): |
|
|
|
return self.detail_url |
|
|
|
|
|
|
|
class CrudDetailView(BaseMixin, DetailView): |
|
|
|
|
|
|
|
@property |
|
|
|
def title(self): |
|
|
|
return self.get_object() |
|
|
|
|
|
|
|
def get_column(self, fieldname, span): |
|
|
|
obj = self.get_object() |
|
|
|
verbose_name, text = get_field_display(obj, fieldname) |
|
|
|
return { |
|
|
|
'id': fieldname, |
|
|
|
'span': span, |
|
|
|
'verbose_name': verbose_name, |
|
|
|
'text': text, |
|
|
|
} |
|
|
|
|
|
|
|
@property |
|
|
|
def fieldsets(self): |
|
|
|
return [ |
|
|
|
{'legend': legend, |
|
|
|
'rows': [[self.get_column(fieldname, span) |
|
|
|
for fieldname, span in row] |
|
|
|
for row in rows] |
|
|
|
} for legend, *rows in layout] |
|
|
|
|
|
|
|
class CrudUpdateView(BaseMixin, FormMessagesMixin, UpdateView): |
|
|
|
form_class = crud.model_form |
|
|
|
form_valid_message = _('Registro alterado com sucesso!') |
|
|
|
form_invalid_message = make_form_invalid_message( |
|
|
|
_('Suas alterações não foram salvas.')) |
|
|
|
|
|
|
|
@property |
|
|
|
def title(self): |
|
|
|
return self.get_object() |
|
|
|
|
|
|
|
def get_success_url(self): |
|
|
|
return self.detail_url |
|
|
|
|
|
|
|
def cancel_url(self): |
|
|
|
return self.detail_url |
|
|
|
|
|
|
|
class CrudDeleteView(BaseMixin, FormMessagesMixin, DeleteView): |
|
|
|
form_valid_message = _('Registro excluído com sucesso!') |
|
|
|
form_invalid_message = make_form_invalid_message( |
|
|
|
_('O registro não foi excluído.')) |
|
|
|
|
|
|
|
def get_success_url(self): |
|
|
|
return self.list_url |
|
|
|
|
|
|
|
crud.CrudListView = CrudListView |
|
|
|
crud.CrudCreateView = CrudCreateView |
|
|
|
crud.CrudDetailView = CrudDetailView |
|
|
|
crud.CrudUpdateView = CrudUpdateView |
|
|
|
crud.CrudDeleteView = CrudDeleteView |
|
|
|
|
|
|
|
# XXX transform into a property of Crud to enable override |
|
|
|
crud.urlpatterns = [ |
|
|
|
url(r'^$', CrudListView.as_view(), name='list'), |
|
|
|
url(r'^create$', CrudCreateView.as_view(), name='create'), |
|
|
|
url(r'^(?P<pk>\d+)$', CrudDetailView.as_view(), name='detail'), |
|
|
|
url(r'^(?P<pk>\d+)/edit$', |
|
|
|
CrudUpdateView.as_view(), name='update'), |
|
|
|
url(r'^(?P<pk>\d+)/delete$', |
|
|
|
CrudDeleteView.as_view(), name='delete'), |
|
|
|
] |
|
|
|
crud.urls = crud.urlpatterns, crud.namespace, crud.namespace |
|
|
|
|
|
|
|
return crud |
|
|
|
|
|
|
|
def __init__(self, model, help_path, layout, |
|
|
|
base_mixin=BaseCrudMixin, |
|
|
|
list_mixin=CrudListMixin, |
|
|
|
create_mixin=CrudCreateMixin, |
|
|
|
detail_mixin=CrudDetailMixin, |
|
|
|
update_mixin=CrudUpdateMixin, |
|
|
|
delete_mixin=CrudDeleteMixin): |
|
|
|
|
|
|
|
class CrudMixin(base_mixin): |
|
|
|
pass |
|
|
|
CrudMixin.model = model |
|
|
|
CrudMixin.help_path = help_path |
|
|
|
CrudMixin.layout = layout |
|
|
|
|
|
|
|
class CrudListView(CrudMixin, list_mixin, ListView): |
|
|
|
pass |
|
|
|
|
|
|
|
class CrudCreateView(CrudMixin, create_mixin, CreateView): |
|
|
|
pass |
|
|
|
|
|
|
|
class CrudDetailView(CrudMixin, detail_mixin, DetailView): |
|
|
|
pass |
|
|
|
|
|
|
|
class CrudUpdateView(CrudMixin, update_mixin, UpdateView): |
|
|
|
pass |
|
|
|
|
|
|
|
class CrudDeleteView(CrudMixin, delete_mixin, DeleteView): |
|
|
|
pass |
|
|
|
|
|
|
|
self.urlpatterns = [ |
|
|
|
url(r'^$', CrudListView.as_view(), name='list'), |
|
|
|
url(r'^create$', CrudCreateView.as_view(), name='create'), |
|
|
|
url(r'^(?P<pk>\d+)$', CrudDetailView.as_view(), name='detail'), |
|
|
|
url(r'^(?P<pk>\d+)/edit$', |
|
|
|
CrudUpdateView.as_view(), name='update'), |
|
|
|
url(r'^(?P<pk>\d+)/delete$', |
|
|
|
CrudDeleteView.as_view(), name='delete'), |
|
|
|
] |
|
|
|
|
|
|
|
self.namespace = CrudMixin().namespace |
|
|
|
self.urls = self.urlpatterns, self.namespace, self.namespace |
|
|
|
|
|
|
|
# FIXME Refatorar código que precisa desse atributo e remover |
|
|
|
self.CrudDetailView = CrudDetailView |
|
|
|