from braces.views import FormMessagesMixin from django.conf.urls import url from django.core.urlresolvers import reverse from django.utils.decorators import classonlymethod from django.utils.translation import ugettext_lazy as _ from django.views.generic import (CreateView, DeleteView, DetailView, ListView, UpdateView) from crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display LIST, CREATE, DETAIL, UPDATE, DELETE = \ 'list', 'create', 'detail', 'update', 'delete' def _form_invalid_message(msg): return '%s %s' % (_('Formulário inválido.'), msg) FORM_MESSAGES = {CREATE: (_('Registro criado com sucesso!'), _('O registro não foi criado.')), UPDATE: (_('Registro alterado com sucesso!'), _('Suas alterações não foram salvas.')), 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 class BaseMixin(CrispyLayoutFormMixin): @classmethod def url_name(cls, suffix): return '%s_%s' % (cls.model._meta.model_name, suffix) def resolve_url(self, suffix, args=None): namespace = self.model._meta.app_label return reverse('%s:%s' % (namespace, self.url_name(suffix)), 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(BaseMixin, 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 CrudListView(ListView): 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): return [self.model._meta.get_field(fieldname).verbose_name for fieldname in self.list_field_names] 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(CrudListView, self).get_context_data(**kwargs) context.setdefault('title', self.verbose_name_plural) # 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 return context class CrudCreateView(FormMessagesMixin, CreateView): form_valid_message, form_invalid_message = FORM_MESSAGES[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) class CrudUpdateView(FormMessagesMixin, UpdateView): form_valid_message, form_invalid_message = FORM_MESSAGES[UPDATE] @property def cancel_url(self): return self.detail_url def get_success_url(self): return self.detail_url class CrudDeleteView(FormMessagesMixin, DeleteView): form_valid_message, form_invalid_message = FORM_MESSAGES[DELETE] @property def cancel_url(self): return self.detail_url def get_success_url(self): return self.list_url class Crud: BaseMixin = BaseMixin ListView = CrudListView CreateView = CrudCreateView DetailView = DetailView UpdateView = CrudUpdateView DeleteView = CrudDeleteView help_path = '' @classonlymethod def get_urls(cls): def _add_base(view): class CrudViewWithBase(cls.BaseMixin, view): model = cls.model help_path = cls.help_path 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) return [url(regex, view.as_view(), name=view.url_name(suffix)) for regex, view, suffix in [ (r'^$', CrudListView, LIST), (r'^create$', CrudCreateView, CREATE), (r'^(?P\d+)$', CrudDetailView, DETAIL), (r'^(?P\d+)/edit$', CrudUpdateView, UPDATE), (r'^(?P\d+)/delete$', CrudDeleteView, DELETE), ]] @classonlymethod def build(cls, _model, _help_path): class ModelCrud(cls): model = _model help_path = _help_path # FIXME!!!! corrigir referencias no codigo e remover isso!!!!! # fazer com #230 class CrudDetailView(cls.BaseMixin, cls.DetailView): model = _model help_path = _help_path ModelCrud.__name__ = '%sCrud' % _model.__name__ return ModelCrud