arrobalytics / django-ledger

Django Ledger is a double entry accounting system built on the Django Web Framework.

Home Page:https://www.djangoledger.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using BaseClass for Views / Forms / Models

NadjibR opened this issue · comments

Is your feature request related to a problem? Please describe.
No.
By checking out the code i've noticed that most of the classes use common fields, properties, contexts

Describe the solution you'd like
I'm workig on a similar project (it's like mini erp system) and I try to use shred base classes as much as I can, to avoid make repeated contexts in views, declare the same field in all models (like id, created_at, create_by, modified_at_modified_by, ...)...

Additional context
Example

A Base Class Model :

class BaseModel(models.Model):
    id = models.AutoField(_('Id'), primary_key=True)
    active = models.BooleanField(_('Active'), default=True, blank=True, null=True)
    created_at = models.DateField( _('Created at'), auto_now_add=True, blank=True, null=True)
    updated_at = models.DateField( _('Updated at'), auto_now=True, blank=True, null=True)
    created_by = models.ForeignKey('base.User', on_delete=models.PROTECT, related_name='%(class)s_created_by', verbose_name=_('Created by'))
    updated_by = models.ForeignKey('base.User', on_delete=models.PROTECT, related_name='%(class)s_updated_by', verbose_name=_('Updated by'))
    base_company = models.ForeignKey('base.Company', on_delete=models.PROTECT, blank=True, null=True, verbose_name=_('Company'), related_name="%(class)s_company")

    class Meta:
        ordering = ['pk']
        abstract = True

Item Model inherits BaseModel :

class Item(BaseModel):
    ...
    class Meta:
      verbose_name = _("Item")
      verbose_name_plural = _("Items")

A Base View regroups all the shared contexts and the template :

class BaseListView(ListView):
    template_name = 'base/list.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        object_name = self.model._meta.object_name
        verbose_name = self.model._meta.verbose_name
        verbose_name_plural = self.model._meta.verbose_name_plural
        context['page_title'] = _('{}'.format(verbose_name_plural))
        context["btn_create_title"] = _('New {}'.format(verbose_name))
        context["btn_update_title"] = _('Update {}'.format(verbose_name))
        context['btn_detail_url'] = '{}:detail'.format(object_name)
        context['add_url'] = reverse_lazy('{}:add'.format(object_name))
        context['edit_url'] = '{}:edit'.format(object_name)
        return context

    def get_queryset(self):
        queryset = self.model.objects.filter()
        return queryset

    class Meta:
        abstract = True

And since all list pages are rendred the same way, (A table with some action buttons) why not using the same template ?

You see that what evev the self.object is in ModelView, it will generate page title, action buttons' titles (Create new ..., Update ..., Delete ...) the same way. What happens if you guys decide to use "Edit .." for the edit button rather than "Update ...", you have to change this in all the Views.

Same things for CreateView, UpdateView, they use the same template (it's a little bit tricky, but I made to work properly)

This way, I can create a new Model -> View -> Form and make it works (List of objects, Create, Update & Delete Object) in few minutes

I'd love to help if I can, let me know what you think.

And this how my shared CreateView looks like :

class BaseCreateView(CreateView):
    template_name = 'stock/item-form.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        verbose_name = self.model._meta.verbose_name
        context["page_title"] = _('Create new {}'.format(verbose_name))
        context["action_type"] = "create"
        return context

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        form.instance.updated_by = self.request.user
        form.instance.created_at = timezone.now()
        form.instance.updated_at = timezone.now()
        form.instance.base_company = self.request.user.current_company
        return super().form_valid(form)

    def get_success_url(self):
        return reverse_lazy('{}:detail'.format(self.model._meta.object_name), kwargs={'pk': self.object.pk})

    class Meta:
        abstract = True

I agree with your approach. Django Ledger makes use of MixIns to encapsulate common behavior among views and avoid repetition. Can you suggest changes to the Django Ledger code to accomplish what you are proposing?

Thanks

The code has been refactored since version 0.4 to incorporate good practices like the one described on this issue.